apiserver.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package apiserver
  14. import (
  15. "bytes"
  16. "encoding/json"
  17. "fmt"
  18. "io"
  19. "io/ioutil"
  20. "net"
  21. "net/http"
  22. "path"
  23. rt "runtime"
  24. "strconv"
  25. "strings"
  26. "time"
  27. "k8s.io/kubernetes/pkg/admission"
  28. "k8s.io/kubernetes/pkg/api"
  29. apierrors "k8s.io/kubernetes/pkg/api/errors"
  30. "k8s.io/kubernetes/pkg/api/meta"
  31. "k8s.io/kubernetes/pkg/api/rest"
  32. "k8s.io/kubernetes/pkg/api/unversioned"
  33. "k8s.io/kubernetes/pkg/apiserver/metrics"
  34. "k8s.io/kubernetes/pkg/runtime"
  35. utilerrors "k8s.io/kubernetes/pkg/util/errors"
  36. "k8s.io/kubernetes/pkg/util/flushwriter"
  37. utilruntime "k8s.io/kubernetes/pkg/util/runtime"
  38. "k8s.io/kubernetes/pkg/util/wsstream"
  39. "k8s.io/kubernetes/pkg/version"
  40. "github.com/emicklei/go-restful"
  41. "github.com/golang/glog"
  42. )
  43. func init() {
  44. metrics.Register()
  45. }
  46. // mux is an object that can register http handlers.
  47. type Mux interface {
  48. Handle(pattern string, handler http.Handler)
  49. HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
  50. }
  51. type APIResourceLister interface {
  52. ListAPIResources() []unversioned.APIResource
  53. }
  54. // APIGroupVersion is a helper for exposing rest.Storage objects as http.Handlers via go-restful
  55. // It handles URLs of the form:
  56. // /${storage_key}[/${object_name}]
  57. // Where 'storage_key' points to a rest.Storage object stored in storage.
  58. // This object should contain all parameterization necessary for running a particular API version
  59. type APIGroupVersion struct {
  60. Storage map[string]rest.Storage
  61. Root string
  62. // GroupVersion is the external group version
  63. GroupVersion unversioned.GroupVersion
  64. // RequestInfoResolver is used to parse URLs for the legacy proxy handler. Don't use this for anything else
  65. // TODO: refactor proxy handler to use sub resources
  66. RequestInfoResolver *RequestInfoResolver
  67. // OptionsExternalVersion controls the Kubernetes APIVersion used for common objects in the apiserver
  68. // schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may
  69. // define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If
  70. // empty, defaults to GroupVersion.
  71. OptionsExternalVersion *unversioned.GroupVersion
  72. Mapper meta.RESTMapper
  73. // Serializer is used to determine how to convert responses from API methods into bytes to send over
  74. // the wire.
  75. Serializer runtime.NegotiatedSerializer
  76. ParameterCodec runtime.ParameterCodec
  77. Typer runtime.ObjectTyper
  78. Creater runtime.ObjectCreater
  79. Convertor runtime.ObjectConvertor
  80. Copier runtime.ObjectCopier
  81. Linker runtime.SelfLinker
  82. Admit admission.Interface
  83. Context api.RequestContextMapper
  84. MinRequestTimeout time.Duration
  85. // SubresourceGroupVersionKind contains the GroupVersionKind overrides for each subresource that is
  86. // accessible from this API group version. The GroupVersionKind is that of the external version of
  87. // the subresource. The key of this map should be the path of the subresource. The keys here should
  88. // match the keys in the Storage map above for subresources.
  89. SubresourceGroupVersionKind map[string]unversioned.GroupVersionKind
  90. // ResourceLister is an interface that knows how to list resources
  91. // for this API Group.
  92. ResourceLister APIResourceLister
  93. }
  94. type ProxyDialerFunc func(network, addr string) (net.Conn, error)
  95. // TODO: Pipe these in through the apiserver cmd line
  96. const (
  97. // Minimum duration before timing out read/write requests
  98. MinTimeoutSecs = 300
  99. // Maximum duration before timing out read/write requests
  100. MaxTimeoutSecs = 600
  101. )
  102. // staticLister implements the APIResourceLister interface
  103. type staticLister struct {
  104. list []unversioned.APIResource
  105. }
  106. func (s staticLister) ListAPIResources() []unversioned.APIResource {
  107. return s.list
  108. }
  109. var _ APIResourceLister = &staticLister{}
  110. // InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
  111. // It is expected that the provided path root prefix will serve all operations. Root MUST NOT end
  112. // in a slash.
  113. func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
  114. installer := g.newInstaller()
  115. ws := installer.NewWebService()
  116. apiResources, registrationErrors := installer.Install(ws)
  117. lister := g.ResourceLister
  118. if lister == nil {
  119. lister = staticLister{apiResources}
  120. }
  121. AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)
  122. container.Add(ws)
  123. return utilerrors.NewAggregate(registrationErrors)
  124. }
  125. // UpdateREST registers the REST handlers for this APIGroupVersion to an existing web service
  126. // in the restful Container. It will use the prefix (root/version) to find the existing
  127. // web service. If a web service does not exist within the container to support the prefix
  128. // this method will return an error.
  129. func (g *APIGroupVersion) UpdateREST(container *restful.Container) error {
  130. installer := g.newInstaller()
  131. var ws *restful.WebService = nil
  132. for i, s := range container.RegisteredWebServices() {
  133. if s.RootPath() == installer.prefix {
  134. ws = container.RegisteredWebServices()[i]
  135. break
  136. }
  137. }
  138. if ws == nil {
  139. return apierrors.NewInternalError(fmt.Errorf("unable to find an existing webservice for prefix %s", installer.prefix))
  140. }
  141. apiResources, registrationErrors := installer.Install(ws)
  142. lister := g.ResourceLister
  143. if lister == nil {
  144. lister = staticLister{apiResources}
  145. }
  146. AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)
  147. return utilerrors.NewAggregate(registrationErrors)
  148. }
  149. // newInstaller is a helper to create the installer. Used by InstallREST and UpdateREST.
  150. func (g *APIGroupVersion) newInstaller() *APIInstaller {
  151. prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
  152. installer := &APIInstaller{
  153. group: g,
  154. info: g.RequestInfoResolver,
  155. prefix: prefix,
  156. minRequestTimeout: g.MinRequestTimeout,
  157. }
  158. return installer
  159. }
  160. // TODO: document all handlers
  161. // InstallVersionHandler registers the APIServer's `/version` handler
  162. func InstallVersionHandler(mux Mux, container *restful.Container) {
  163. // Set up a service to return the git code version.
  164. versionWS := new(restful.WebService)
  165. versionWS.Path("/version")
  166. versionWS.Doc("git code version from which this is built")
  167. versionWS.Route(
  168. versionWS.GET("/").To(handleVersion).
  169. Doc("get the code version").
  170. Operation("getCodeVersion").
  171. Produces(restful.MIME_JSON).
  172. Consumes(restful.MIME_JSON).
  173. Writes(version.Info{}))
  174. container.Add(versionWS)
  175. }
  176. // InstallLogsSupport registers the APIServer's `/logs` into a mux.
  177. func InstallLogsSupport(mux Mux, container *restful.Container) {
  178. // use restful: ws.Route(ws.GET("/logs/{logpath:*}").To(fileHandler))
  179. // See github.com/emicklei/go-restful/blob/master/examples/restful-serve-static.go
  180. ws := new(restful.WebService)
  181. ws.Path("/logs")
  182. ws.Doc("get log files")
  183. ws.Route(ws.GET("/{logpath:*}").To(logFileHandler))
  184. ws.Route(ws.GET("/").To(logFileListHandler))
  185. container.Add(ws)
  186. }
  187. func logFileHandler(req *restful.Request, resp *restful.Response) {
  188. logdir := "/var/log"
  189. actual := path.Join(logdir, req.PathParameter("logpath"))
  190. http.ServeFile(resp.ResponseWriter, req.Request, actual)
  191. }
  192. func logFileListHandler(req *restful.Request, resp *restful.Response) {
  193. logdir := "/var/log"
  194. http.ServeFile(resp.ResponseWriter, req.Request, logdir)
  195. }
  196. // TODO: needs to perform response type negotiation, this is probably the wrong way to recover panics
  197. func InstallRecoverHandler(s runtime.NegotiatedSerializer, container *restful.Container) {
  198. container.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) {
  199. logStackOnRecover(s, panicReason, httpWriter)
  200. })
  201. }
  202. //TODO: Unify with RecoverPanics?
  203. func logStackOnRecover(s runtime.NegotiatedSerializer, panicReason interface{}, w http.ResponseWriter) {
  204. var buffer bytes.Buffer
  205. buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason))
  206. for i := 2; ; i += 1 {
  207. _, file, line, ok := rt.Caller(i)
  208. if !ok {
  209. break
  210. }
  211. buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
  212. }
  213. glog.Errorln(buffer.String())
  214. headers := http.Header{}
  215. if ct := w.Header().Get("Content-Type"); len(ct) > 0 {
  216. headers.Set("Accept", ct)
  217. }
  218. errorNegotiated(apierrors.NewGenericServerResponse(http.StatusInternalServerError, "", api.Resource(""), "", "", 0, false), s, unversioned.GroupVersion{}, w, &http.Request{Header: headers})
  219. }
  220. func InstallServiceErrorHandler(s runtime.NegotiatedSerializer, container *restful.Container, requestResolver *RequestInfoResolver, apiVersions []string) {
  221. container.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
  222. serviceErrorHandler(s, requestResolver, apiVersions, serviceErr, request, response)
  223. })
  224. }
  225. func serviceErrorHandler(s runtime.NegotiatedSerializer, requestResolver *RequestInfoResolver, apiVersions []string, serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
  226. errorNegotiated(
  227. apierrors.NewGenericServerResponse(serviceErr.Code, "", api.Resource(""), "", serviceErr.Message, 0, false),
  228. s,
  229. unversioned.GroupVersion{},
  230. response.ResponseWriter,
  231. request.Request,
  232. )
  233. }
  234. // Adds a service to return the supported api versions at the legacy /api.
  235. func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, getAPIVersionsFunc func(req *restful.Request) *unversioned.APIVersions) {
  236. // TODO: InstallREST should register each version automatically
  237. // Because in release 1.1, /api returns response with empty APIVersion, we
  238. // use StripVersionNegotiatedSerializer to keep the response backwards
  239. // compatible.
  240. ss := StripVersionNegotiatedSerializer{s}
  241. versionHandler := APIVersionHandler(ss, getAPIVersionsFunc)
  242. ws := new(restful.WebService)
  243. ws.Path(apiPrefix)
  244. ws.Doc("get available API versions")
  245. ws.Route(ws.GET("/").To(versionHandler).
  246. Doc("get available API versions").
  247. Operation("getAPIVersions").
  248. Produces(s.SupportedMediaTypes()...).
  249. Consumes(s.SupportedMediaTypes()...).
  250. Writes(unversioned.APIVersions{}))
  251. container.Add(ws)
  252. }
  253. // stripVersionEncoder strips APIVersion field from the encoding output. It's
  254. // used to keep the responses at the discovery endpoints backward compatible
  255. // with release-1.1, when the responses have empty APIVersion.
  256. type stripVersionEncoder struct {
  257. encoder runtime.Encoder
  258. serializer runtime.Serializer
  259. }
  260. func (c stripVersionEncoder) Encode(obj runtime.Object, w io.Writer) error {
  261. buf := bytes.NewBuffer([]byte{})
  262. err := c.encoder.Encode(obj, buf)
  263. if err != nil {
  264. return err
  265. }
  266. roundTrippedObj, gvk, err := c.serializer.Decode(buf.Bytes(), nil, nil)
  267. if err != nil {
  268. return err
  269. }
  270. gvk.Group = ""
  271. gvk.Version = ""
  272. roundTrippedObj.GetObjectKind().SetGroupVersionKind(*gvk)
  273. return c.serializer.Encode(roundTrippedObj, w)
  274. }
  275. // StripVersionNegotiatedSerializer will return stripVersionEncoder when
  276. // EncoderForVersion is called. See comments for stripVersionEncoder.
  277. type StripVersionNegotiatedSerializer struct {
  278. runtime.NegotiatedSerializer
  279. }
  280. func (n StripVersionNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
  281. serializer, ok := encoder.(runtime.Serializer)
  282. if !ok {
  283. // The stripVersionEncoder needs both an encoder and decoder, but is called from a context that doesn't have access to the
  284. // decoder. We do a best effort cast here (since this code path is only for backwards compatibility) to get access to the caller's
  285. // decoder.
  286. panic(fmt.Sprintf("Unable to extract serializer from %#v", encoder))
  287. }
  288. versioned := n.NegotiatedSerializer.EncoderForVersion(encoder, gv)
  289. return stripVersionEncoder{versioned, serializer}
  290. }
  291. func keepUnversioned(group string) bool {
  292. return group == "" || group == "extensions"
  293. }
  294. // Adds a service to return the supported api versions at /apis.
  295. func AddApisWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, f func(req *restful.Request) []unversioned.APIGroup) {
  296. // Because in release 1.1, /apis returns response with empty APIVersion, we
  297. // use StripVersionNegotiatedSerializer to keep the response backwards
  298. // compatible.
  299. ss := StripVersionNegotiatedSerializer{s}
  300. rootAPIHandler := RootAPIHandler(ss, f)
  301. ws := new(restful.WebService)
  302. ws.Path(apiPrefix)
  303. ws.Doc("get available API versions")
  304. ws.Route(ws.GET("/").To(rootAPIHandler).
  305. Doc("get available API versions").
  306. Operation("getAPIVersions").
  307. Produces(s.SupportedMediaTypes()...).
  308. Consumes(s.SupportedMediaTypes()...).
  309. Writes(unversioned.APIGroupList{}))
  310. container.Add(ws)
  311. }
  312. // Adds a service to return the supported versions, preferred version, and name
  313. // of a group. E.g., a such web service will be registered at /apis/extensions.
  314. func AddGroupWebService(s runtime.NegotiatedSerializer, container *restful.Container, path string, group unversioned.APIGroup) {
  315. ss := s
  316. if keepUnversioned(group.Name) {
  317. // Because in release 1.1, /apis/extensions returns response with empty
  318. // APIVersion, we use StripVersionNegotiatedSerializer to keep the
  319. // response backwards compatible.
  320. ss = StripVersionNegotiatedSerializer{s}
  321. }
  322. groupHandler := GroupHandler(ss, group)
  323. ws := new(restful.WebService)
  324. ws.Path(path)
  325. ws.Doc("get information of a group")
  326. ws.Route(ws.GET("/").To(groupHandler).
  327. Doc("get information of a group").
  328. Operation("getAPIGroup").
  329. Produces(s.SupportedMediaTypes()...).
  330. Consumes(s.SupportedMediaTypes()...).
  331. Writes(unversioned.APIGroup{}))
  332. container.Add(ws)
  333. }
  334. // Adds a service to return the supported resources, E.g., a such web service
  335. // will be registered at /apis/extensions/v1.
  336. func AddSupportedResourcesWebService(s runtime.NegotiatedSerializer, ws *restful.WebService, groupVersion unversioned.GroupVersion, lister APIResourceLister) {
  337. ss := s
  338. if keepUnversioned(groupVersion.Group) {
  339. // Because in release 1.1, /apis/extensions/v1beta1 returns response
  340. // with empty APIVersion, we use StripVersionNegotiatedSerializer to
  341. // keep the response backwards compatible.
  342. ss = StripVersionNegotiatedSerializer{s}
  343. }
  344. resourceHandler := SupportedResourcesHandler(ss, groupVersion, lister)
  345. ws.Route(ws.GET("/").To(resourceHandler).
  346. Doc("get available resources").
  347. Operation("getAPIResources").
  348. Produces(s.SupportedMediaTypes()...).
  349. Consumes(s.SupportedMediaTypes()...).
  350. Writes(unversioned.APIResourceList{}))
  351. }
  352. // handleVersion writes the server's version information.
  353. func handleVersion(req *restful.Request, resp *restful.Response) {
  354. writeRawJSON(http.StatusOK, version.Get(), resp.ResponseWriter)
  355. }
  356. // APIVersionHandler returns a handler which will list the provided versions as available.
  357. func APIVersionHandler(s runtime.NegotiatedSerializer, getAPIVersionsFunc func(req *restful.Request) *unversioned.APIVersions) restful.RouteFunction {
  358. return func(req *restful.Request, resp *restful.Response) {
  359. writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, getAPIVersionsFunc(req))
  360. }
  361. }
  362. // RootAPIHandler returns a handler which will list the provided groups and versions as available.
  363. func RootAPIHandler(s runtime.NegotiatedSerializer, f func(req *restful.Request) []unversioned.APIGroup) restful.RouteFunction {
  364. return func(req *restful.Request, resp *restful.Response) {
  365. writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &unversioned.APIGroupList{Groups: f(req)})
  366. }
  367. }
  368. // GroupHandler returns a handler which will return the api.GroupAndVersion of
  369. // the group.
  370. func GroupHandler(s runtime.NegotiatedSerializer, group unversioned.APIGroup) restful.RouteFunction {
  371. return func(req *restful.Request, resp *restful.Response) {
  372. writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &group)
  373. }
  374. }
  375. // SupportedResourcesHandler returns a handler which will list the provided resources as available.
  376. func SupportedResourcesHandler(s runtime.NegotiatedSerializer, groupVersion unversioned.GroupVersion, lister APIResourceLister) restful.RouteFunction {
  377. return func(req *restful.Request, resp *restful.Response) {
  378. writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &unversioned.APIResourceList{GroupVersion: groupVersion.String(), APIResources: lister.ListAPIResources()})
  379. }
  380. }
  381. // write renders a returned runtime.Object to the response as a stream or an encoded object. If the object
  382. // returned by the response implements rest.ResourceStreamer that interface will be used to render the
  383. // response. The Accept header and current API version will be passed in, and the output will be copied
  384. // directly to the response body. If content type is returned it is used, otherwise the content type will
  385. // be "application/octet-stream". All other objects are sent to standard JSON serialization.
  386. func write(statusCode int, gv unversioned.GroupVersion, s runtime.NegotiatedSerializer, object runtime.Object, w http.ResponseWriter, req *http.Request) {
  387. if stream, ok := object.(rest.ResourceStreamer); ok {
  388. out, flush, contentType, err := stream.InputStream(gv.String(), req.Header.Get("Accept"))
  389. if err != nil {
  390. errorNegotiated(err, s, gv, w, req)
  391. return
  392. }
  393. if out == nil {
  394. // No output provided - return StatusNoContent
  395. w.WriteHeader(http.StatusNoContent)
  396. return
  397. }
  398. defer out.Close()
  399. if wsstream.IsWebSocketRequest(req) {
  400. r := wsstream.NewReader(out, true, wsstream.NewDefaultReaderProtocols())
  401. if err := r.Copy(w, req); err != nil {
  402. utilruntime.HandleError(fmt.Errorf("error encountered while streaming results via websocket: %v", err))
  403. }
  404. return
  405. }
  406. if len(contentType) == 0 {
  407. contentType = "application/octet-stream"
  408. }
  409. w.Header().Set("Content-Type", contentType)
  410. w.WriteHeader(statusCode)
  411. writer := w.(io.Writer)
  412. if flush {
  413. writer = flushwriter.Wrap(w)
  414. }
  415. io.Copy(writer, out)
  416. return
  417. }
  418. writeNegotiated(s, gv, w, req, statusCode, object)
  419. }
  420. // writeNegotiated renders an object in the content type negotiated by the client
  421. func writeNegotiated(s runtime.NegotiatedSerializer, gv unversioned.GroupVersion, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
  422. serializer, err := negotiateOutputSerializer(req, s)
  423. if err != nil {
  424. status := errToAPIStatus(err)
  425. writeRawJSON(int(status.Code), status, w)
  426. return
  427. }
  428. w.Header().Set("Content-Type", serializer.MediaType)
  429. w.WriteHeader(statusCode)
  430. encoder := s.EncoderForVersion(serializer, gv)
  431. if err := encoder.Encode(object, w); err != nil {
  432. errorJSONFatal(err, encoder, w)
  433. }
  434. }
  435. // errorNegotiated renders an error to the response. Returns the HTTP status code of the error.
  436. func errorNegotiated(err error, s runtime.NegotiatedSerializer, gv unversioned.GroupVersion, w http.ResponseWriter, req *http.Request) int {
  437. status := errToAPIStatus(err)
  438. code := int(status.Code)
  439. // when writing an error, check to see if the status indicates a retry after period
  440. if status.Details != nil && status.Details.RetryAfterSeconds > 0 {
  441. delay := strconv.Itoa(int(status.Details.RetryAfterSeconds))
  442. w.Header().Set("Retry-After", delay)
  443. }
  444. writeNegotiated(s, gv, w, req, code, status)
  445. return code
  446. }
  447. // errorJSONFatal renders an error to the response, and if codec fails will render plaintext.
  448. // Returns the HTTP status code of the error.
  449. func errorJSONFatal(err error, codec runtime.Encoder, w http.ResponseWriter) int {
  450. utilruntime.HandleError(fmt.Errorf("apiserver was unable to write a JSON response: %v", err))
  451. status := errToAPIStatus(err)
  452. code := int(status.Code)
  453. output, err := runtime.Encode(codec, status)
  454. if err != nil {
  455. w.WriteHeader(code)
  456. fmt.Fprintf(w, "%s: %s", status.Reason, status.Message)
  457. return code
  458. }
  459. w.Header().Set("Content-Type", "application/json")
  460. w.WriteHeader(code)
  461. w.Write(output)
  462. return code
  463. }
  464. // writeRawJSON writes a non-API object in JSON.
  465. func writeRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
  466. output, err := json.MarshalIndent(object, "", " ")
  467. if err != nil {
  468. http.Error(w, err.Error(), http.StatusInternalServerError)
  469. return
  470. }
  471. w.Header().Set("Content-Type", "application/json")
  472. w.WriteHeader(statusCode)
  473. w.Write(output)
  474. }
  475. func parseTimeout(str string) time.Duration {
  476. if str != "" {
  477. timeout, err := time.ParseDuration(str)
  478. if err == nil {
  479. return timeout
  480. }
  481. glog.Errorf("Failed to parse %q: %v", str, err)
  482. }
  483. return 30 * time.Second
  484. }
  485. func readBody(req *http.Request) ([]byte, error) {
  486. defer req.Body.Close()
  487. return ioutil.ReadAll(req.Body)
  488. }
  489. // splitPath returns the segments for a URL path.
  490. func splitPath(path string) []string {
  491. path = strings.Trim(path, "/")
  492. if path == "" {
  493. return []string{}
  494. }
  495. return strings.Split(path, "/")
  496. }