func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { if obj == nil { return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) } scale, ok := obj.(*extensions.Scale) if !ok { return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj)) } deployment, err := (*r.registry).GetDeployment(ctx, scale.Name) if err != nil { return nil, false, errors.NewNotFound("scale", scale.Name) } deployment.Spec.Replicas = scale.Spec.Replicas deployment, err = (*r.registry).UpdateDeployment(ctx, deployment) if err != nil { return nil, false, errors.NewConflict("scale", scale.Name, err) } return &extensions.Scale{ ObjectMeta: api.ObjectMeta{ Name: deployment.Name, Namespace: deployment.Namespace, CreationTimestamp: deployment.CreationTimestamp, }, Spec: extensions.ScaleSpec{ Replicas: deployment.Spec.Replicas, }, Status: extensions.ScaleStatus{ Replicas: deployment.Status.Replicas, Selector: deployment.Spec.Selector, }, }, false, nil }
// LogLocation returns the log URL for a pod container. If opts.Container is blank // and only one container is present in the pod, that container is used. func LogLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string, opts *api.PodLogOptions) (*url.URL, http.RoundTripper, error) { pod, err := getPod(getter, ctx, name) if err != nil { return nil, nil, err } // Try to figure out a container // If a container was provided, it must be valid container := opts.Container if len(container) == 0 { if len(pod.Spec.Containers) == 1 { container = pod.Spec.Containers[0].Name } else { return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name)) } } else { if !podHasContainerWithName(pod, container) { return nil, nil, errors.NewBadRequest(fmt.Sprintf("container %s is not valid for pod %s", container, name)) } } nodeHost := pod.Spec.NodeName if len(nodeHost) == 0 { // If pod has not been assigned a host, return an empty location return nil, nil, nil } nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost) if err != nil { return nil, nil, err } params := url.Values{} if opts.Follow { params.Add("follow", "true") } if opts.Previous { params.Add("previous", "true") } if opts.Timestamps { params.Add("timestamps", "true") } if opts.SinceSeconds != nil { params.Add("sinceSeconds", strconv.FormatInt(*opts.SinceSeconds, 10)) } if opts.SinceTime != nil { params.Add("sinceTime", opts.SinceTime.Format(time.RFC3339)) } if opts.TailLines != nil { params.Add("tailLines", strconv.FormatInt(*opts.TailLines, 10)) } if opts.LimitBytes != nil { params.Add("limitBytes", strconv.FormatInt(*opts.LimitBytes, 10)) } loc := &url.URL{ Scheme: nodeScheme, Host: fmt.Sprintf("%s:%d", nodeHost, nodePort), Path: fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, pod.Name, container), RawQuery: params.Encode(), } return loc, nodeTransport, nil }
// transformDecodeError adds additional information when a decode fails. func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime.Object, body []byte) error { _, kind, err := typer.ObjectVersionAndKind(into) if err != nil { return err } if version, dataKind, err := typer.DataVersionAndKind(body); err == nil && len(dataKind) > 0 { return errors.NewBadRequest(fmt.Sprintf("%s in version %s cannot be handled as a %s: %v", dataKind, version, kind, baseErr)) } return errors.NewBadRequest(fmt.Sprintf("the object provided is unrecognized (must be of type %s): %v", kind, baseErr)) }
// NoNamespaceKeyFunc is the default function for constructing etcd paths to a resource relative to prefix without a namespace func NoNamespaceKeyFunc(ctx api.Context, prefix string, name string) (string, error) { if len(name) == 0 { return "", kubeerr.NewBadRequest("Name parameter required.") } if ok, msg := validation.ValidatePathSegmentName(name, false); !ok { return "", kubeerr.NewBadRequest(fmt.Sprintf("Name parameter invalid: %v.", msg)) } key := prefix + "/" + name return key, nil }
// NamespaceKeyFunc is the default function for constructing etcd paths to a resource relative to prefix enforcing namespace rules. // If no namespace is on context, it errors. func NamespaceKeyFunc(ctx api.Context, prefix string, name string) (string, error) { key := NamespaceKeyRootFunc(ctx, prefix) ns, ok := api.NamespaceFrom(ctx) if !ok || len(ns) == 0 { return "", kubeerr.NewBadRequest("Namespace parameter required.") } if len(name) == 0 { return "", kubeerr.NewBadRequest("Name parameter required.") } if ok, msg := validation.ValidatePathSegmentName(name, false); !ok { return "", kubeerr.NewBadRequest(fmt.Sprintf("Name parameter invalid: %v.", msg)) } key = key + "/" + name return key, nil }
// ResourceLocation returns an URL and transport which one can use to send traffic for the specified node. func ResourceLocation(getter ResourceGetter, connection client.ConnectionInfoGetter, proxyTransport http.RoundTripper, ctx api.Context, id string) (*url.URL, http.RoundTripper, error) { schemeReq, name, portReq, valid := util.SplitSchemeNamePort(id) if !valid { return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid node request %q", id)) } nodeObj, err := getter.Get(ctx, name) if err != nil { return nil, nil, err } node := nodeObj.(*api.Node) hostIP, err := nodeutil.GetNodeHostIP(node) if err != nil { return nil, nil, err } host := hostIP.String() if portReq == "" || strconv.Itoa(ports.KubeletPort) == portReq { // Ignore requested scheme, use scheme provided by GetConnectionInfo scheme, port, kubeletTransport, err := connection.GetConnectionInfo(host) if err != nil { return nil, nil, err } return &url.URL{ Scheme: scheme, Host: net.JoinHostPort( host, strconv.FormatUint(uint64(port), 10), ), }, kubeletTransport, nil } return &url.URL{Scheme: schemeReq, Host: net.JoinHostPort(host, portReq)}, proxyTransport, nil }
func (d *denyExec) Admit(a admission.Attributes) (err error) { connectRequest, ok := a.GetObject().(*rest.ConnectRequest) if !ok { return errors.NewBadRequest("a connect request was received, but could not convert the request object.") } // Only handle exec or attach requests on pods if connectRequest.ResourcePath != "pods/exec" && connectRequest.ResourcePath != "pods/attach" { return nil } pod, err := d.client.Pods(a.GetNamespace()).Get(connectRequest.Name) if err != nil { return admission.NewForbidden(a, err) } if d.hostPID && pod.Spec.HostPID { return admission.NewForbidden(a, fmt.Errorf("Cannot exec into or attach to a container using host pid")) } if d.hostIPC && pod.Spec.HostIPC { return admission.NewForbidden(a, fmt.Errorf("Cannot exec into or attach to a container using host ipc")) } if d.privileged && isPrivileged(pod) { return admission.NewForbidden(a, fmt.Errorf("Cannot exec into or attach to a privileged container")) } return nil }
// ResourceLocation returns a URL to which one can send traffic for the specified pod. func ResourceLocation(getter ResourceGetter, rt http.RoundTripper, ctx api.Context, id string) (*url.URL, http.RoundTripper, error) { // Allow ID as "podname" or "podname:port" or "scheme:podname:port". // If port is not specified, try to use the first defined port on the pod. scheme, name, port, valid := util.SplitSchemeNamePort(id) if !valid { return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid pod request %q", id)) } // TODO: if port is not a number but a "(container)/(portname)", do a name lookup. pod, err := getPod(getter, ctx, name) if err != nil { return nil, nil, err } // Try to figure out a port. if port == "" { for i := range pod.Spec.Containers { if len(pod.Spec.Containers[i].Ports) > 0 { port = fmt.Sprintf("%d", pod.Spec.Containers[i].Ports[0].ContainerPort) break } } } loc := &url.URL{ Scheme: scheme, } if port == "" { loc.Host = pod.Status.PodIP } else { loc.Host = net.JoinHostPort(pod.Status.PodIP, port) } return loc, rt, nil }
// BeforeCreate ensures that common operations for all resources are performed on creation. It only returns // errors that can be converted to api.Status. It invokes PrepareForCreate, then GenerateName, then Validate. // It returns nil if the object should be created. func BeforeCreate(strategy RESTCreateStrategy, ctx api.Context, obj runtime.Object) error { objectMeta, kind, kerr := objectMetaAndKind(strategy, obj) if kerr != nil { return kerr } if strategy.NamespaceScoped() { if !api.ValidNamespace(ctx, objectMeta) { return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request") } } else { objectMeta.Namespace = api.NamespaceNone } objectMeta.DeletionTimestamp = nil objectMeta.DeletionGracePeriodSeconds = nil strategy.PrepareForCreate(obj) api.FillObjectMetaSystemFields(ctx, objectMeta) api.GenerateName(strategy, objectMeta) if errs := strategy.Validate(ctx, obj); len(errs) > 0 { return errors.NewInvalid(kind, objectMeta.Name, errs) } // Custom validation (including name validation) passed // Now run common validation on object meta // Do this *after* custom validation so that specific error messages are shown whenever possible if errs := validation.ValidateObjectMeta(objectMeta, strategy.NamespaceScoped(), validation.ValidatePathSegmentName); len(errs) > 0 { return errors.NewInvalid(kind, objectMeta.Name, errs) } return nil }
// checkName checks the provided name against the request func checkName(obj runtime.Object, name, namespace string, namer ScopeNamer) error { if objNamespace, objName, err := namer.ObjectName(obj); err == nil { if err != nil { return err } if objName != name { return errors.NewBadRequest(fmt.Sprintf( "the name of the object (%s) does not match the name on the URL (%s)", objName, name)) } if len(namespace) > 0 { if len(objNamespace) > 0 && objNamespace != namespace { return errors.NewBadRequest(fmt.Sprintf( "the namespace of the object (%s) does not match the namespace on the request (%s)", objNamespace, namespace)) } } } return nil }
func streamLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string, opts runtime.Object, container, path string) (*url.URL, http.RoundTripper, error) { pod, err := getPod(getter, ctx, name) if err != nil { return nil, nil, err } // Try to figure out a container // If a container was provided, it must be valid if container == "" { if len(pod.Spec.Containers) == 1 { container = pod.Spec.Containers[0].Name } else { return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name)) } } else { if !podHasContainerWithName(pod, container) { return nil, nil, errors.NewBadRequest(fmt.Sprintf("container %s is not valid for pod %s", container, name)) } } nodeHost := pod.Spec.NodeName if len(nodeHost) == 0 { // If pod has not been assigned a host, return an empty location return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name)) } nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost) if err != nil { return nil, nil, err } params := url.Values{} if err := streamParams(params, opts); err != nil { return nil, nil, err } loc := &url.URL{ Scheme: nodeScheme, Host: fmt.Sprintf("%s:%d", nodeHost, nodePort), Path: fmt.Sprintf("/%s/%s/%s/%s", path, pod.Namespace, pod.Name, container), RawQuery: params.Encode(), } return loc, nodeTransport, nil }
// ServeHTTP handles the proxy request func (h *UpgradeAwareProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { h.err = nil if len(h.Location.Scheme) == 0 { h.Location.Scheme = "http" } if h.tryUpgrade(w, req) { return } if h.UpgradeRequired { h.err = errors.NewBadRequest("Upgrade request required") return } loc := *h.Location loc.RawQuery = req.URL.RawQuery // If original request URL ended in '/', append a '/' at the end of the // of the proxy URL if !strings.HasSuffix(loc.Path, "/") && strings.HasSuffix(req.URL.Path, "/") { loc.Path += "/" } // From pkg/apiserver/proxy.go#ServeHTTP: // Redirect requests with an empty path to a location that ends with a '/' // This is essentially a hack for http://issue.k8s.io/4958. // Note: Keep this code after tryUpgrade to not break that flow. if len(loc.Path) == 0 { var queryPart string if len(req.URL.RawQuery) > 0 { queryPart = "?" + req.URL.RawQuery } w.Header().Set("Location", req.URL.Path+"/"+queryPart) w.WriteHeader(http.StatusMovedPermanently) return } if h.Transport == nil || h.WrapTransport { h.Transport = h.defaultProxyTransport(req.URL, h.Transport) } newReq, err := http.NewRequest(req.Method, loc.String(), req.Body) if err != nil { h.err = err return } newReq.Header = req.Header proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: h.Location.Scheme, Host: h.Location.Host}) proxy.Transport = h.Transport proxy.FlushInterval = h.FlushInterval proxy.ServeHTTP(w, newReq) }
func (ir initialResources) Admit(a admission.Attributes) (err error) { // Ignore all calls to subresources or resources other than pods. if a.GetSubresource() != "" || a.GetResource() != string(api.ResourcePods) { return nil } pod, ok := a.GetObject().(*api.Pod) if !ok { return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted") } ir.estimateAndFillResourcesIfNotSet(pod) return nil }
// queryToObject converts query parameters into a structured internal object by // kind. The caller must cast the returned object to the matching internal Kind // to use it. // TODO: add appropriate structured error responses func queryToObject(query url.Values, scope RequestScope, kind string) (runtime.Object, error) { versioned, err := scope.Creater.New(scope.ServerAPIVersion, kind) if err != nil { // programmer error return nil, err } if err := scope.Convertor.Convert(&query, versioned); err != nil { return nil, errors.NewBadRequest(err.Error()) } out, err := scope.Convertor.ConvertToVersion(versioned, "") if err != nil { // programmer error return nil, err } return out, nil }
// BeforeUpdate ensures that common operations for all resources are performed on update. It only returns // errors that can be converted to api.Status. It will invoke update validation with the provided existing // and updated objects. func BeforeUpdate(strategy RESTUpdateStrategy, ctx api.Context, obj, old runtime.Object) error { objectMeta, kind, kerr := objectMetaAndKind(strategy, obj) if kerr != nil { return kerr } if strategy.NamespaceScoped() { if !api.ValidNamespace(ctx, objectMeta) { return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request") } } else { objectMeta.Namespace = api.NamespaceNone } strategy.PrepareForUpdate(obj, old) if errs := strategy.ValidateUpdate(ctx, obj, old); len(errs) > 0 { return errors.NewInvalid(kind, objectMeta.Name, errs) } return nil }
// Admit will deny any SecurityContext that defines options that were not previously available in the api.Container // struct (Capabilities and Privileged) func (p *plugin) Admit(a admission.Attributes) (err error) { if a.GetResource() != string(api.ResourcePods) { return nil } pod, ok := a.GetObject().(*api.Pod) if !ok { return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted") } for _, v := range pod.Spec.Containers { if v.SecurityContext != nil { if v.SecurityContext.SELinuxOptions != nil { return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("SecurityContext.SELinuxOptions is forbidden")) } if v.SecurityContext.RunAsUser != nil { return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("SecurityContext.RunAsUser is forbidden")) } } } return nil }
// PortForwardLocation returns the port-forward URL for a pod. func PortForwardLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string) (*url.URL, http.RoundTripper, error) { pod, err := getPod(getter, ctx, name) if err != nil { return nil, nil, err } nodeHost := pod.Spec.NodeName if len(nodeHost) == 0 { // If pod has not been assigned a host, return an empty location return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name)) } nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost) if err != nil { return nil, nil, err } loc := &url.URL{ Scheme: nodeScheme, Host: fmt.Sprintf("%s:%d", nodeHost, nodePort), Path: fmt.Sprintf("/portForward/%s/%s", pod.Namespace, pod.Name), } return loc, nodeTransport, nil }
// ResourceLocation returns a URL to which one can send traffic for the specified service. func (rs *REST) ResourceLocation(ctx api.Context, id string) (*url.URL, http.RoundTripper, error) { // Allow ID as "svcname", "svcname:port", or "scheme:svcname:port". svcScheme, svcName, portStr, valid := util.SplitSchemeNamePort(id) if !valid { return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid service request %q", id)) } eps, err := rs.endpoints.GetEndpoints(ctx, svcName) if err != nil { return nil, nil, err } if len(eps.Subsets) == 0 { return nil, nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", svcName)) } // Pick a random Subset to start searching from. ssSeed := rand.Intn(len(eps.Subsets)) // Find a Subset that has the port. for ssi := 0; ssi < len(eps.Subsets); ssi++ { ss := &eps.Subsets[(ssSeed+ssi)%len(eps.Subsets)] if len(ss.Addresses) == 0 { continue } for i := range ss.Ports { if ss.Ports[i].Name == portStr { // Pick a random address. ip := ss.Addresses[rand.Intn(len(ss.Addresses))].IP port := ss.Ports[i].Port return &url.URL{ Scheme: svcScheme, Host: net.JoinHostPort(ip, strconv.Itoa(port)), }, rs.proxyTransport, nil } } } return nil, nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", id)) }
// BeforeUpdate ensures that common operations for all resources are performed on update. It only returns // errors that can be converted to api.Status. It will invoke update validation with the provided existing // and updated objects. func BeforeUpdate(strategy RESTUpdateStrategy, ctx api.Context, obj, old runtime.Object) error { objectMeta, kind, kerr := objectMetaAndKind(strategy, obj) if kerr != nil { return kerr } if strategy.NamespaceScoped() { if !api.ValidNamespace(ctx, objectMeta) { return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request") } } else { objectMeta.Namespace = api.NamespaceNone } strategy.PrepareForUpdate(obj, old) // Ensure some common fields, like UID, are validated for all resources. errs := validateCommonFields(obj, old) errs = append(errs, strategy.ValidateUpdate(ctx, obj, old)...) if len(errs) > 0 { return errors.NewInvalid(kind, objectMeta.Name, errs) } return nil }
func TestCheckLogLocation(t *testing.T) { ctx := api.NewDefaultContext() tcs := []struct { in *api.Pod opts *api.PodLogOptions expectedErr error }{ { in: &api.Pod{ Spec: api.PodSpec{}, Status: api.PodStatus{}, }, opts: &api.PodLogOptions{}, expectedErr: errors.NewBadRequest("a container name must be specified for pod test"), }, { in: &api.Pod{ Spec: api.PodSpec{ Containers: []api.Container{ {Name: "mycontainer"}, }, }, Status: api.PodStatus{}, }, opts: &api.PodLogOptions{}, expectedErr: nil, }, { in: &api.Pod{ Spec: api.PodSpec{ Containers: []api.Container{ {Name: "container1"}, {Name: "container2"}, }, }, Status: api.PodStatus{}, }, opts: &api.PodLogOptions{}, expectedErr: errors.NewBadRequest("a container name must be specified for pod test"), }, { in: &api.Pod{ Spec: api.PodSpec{ Containers: []api.Container{ {Name: "container1"}, {Name: "container2"}, }, }, Status: api.PodStatus{}, }, opts: &api.PodLogOptions{ Container: "unknown", }, expectedErr: errors.NewBadRequest("container unknown is not valid for pod test"), }, { in: &api.Pod{ Spec: api.PodSpec{ Containers: []api.Container{ {Name: "container1"}, {Name: "container2"}, }, }, Status: api.PodStatus{}, }, opts: &api.PodLogOptions{ Container: "container2", }, expectedErr: nil, }, } for _, tc := range tcs { getter := &mockPodGetter{tc.in} _, _, err := LogLocation(getter, nil, ctx, "test", tc.opts) if !reflect.DeepEqual(err, tc.expectedErr) { t.Errorf("expected %v, got %v", tc.expectedErr, err) } } }
// Struct capturing information about an action ("GET", "POST", "WATCH", PROXY", etc). type action struct { Verb string // Verb identifying the action ("GET", "POST", "WATCH", PROXY", etc). Path string // The path of the action Params []*restful.Parameter // List of parameters associated with the action. Namer ScopeNamer } // An interface to see if an object supports swagger documentation as a method type documentable interface { SwaggerDoc() map[string]string } // errEmptyName is returned when API requests do not fill the name section of the path. var errEmptyName = errors.NewBadRequest("name must be provided") // Installs handlers for API resources. func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []unversioned.APIResource, errors []error) { errors = make([]error, 0) proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.Storage, a.group.Codec, a.group.Context, a.info}) // Register the paths in a deterministic (sorted) order to get a deterministic swagger spec. paths := make([]string, len(a.group.Storage)) var i int = 0 for path := range a.group.Storage { paths[i] = path i++ } sort.Strings(paths)
// ListResource returns a function that handles retrieving a list of resources from a rest.Storage object. func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter namespace, err := scope.Namer.Namespace(req) if err != nil { errorJSON(err, scope.Codec, w) return } // Watches for single objects are routed to this function. // Treat a /name parameter the same as a field selector entry. hasName := true _, name, err := scope.Namer.Name(req) if err != nil { hasName = false } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) out, err := queryToObject(req.Request.URL.Query(), scope, "ListOptions") if err != nil { errorJSON(err, scope.Codec, w) return } opts := *out.(*api.ListOptions) // transform fields // TODO: queryToObject should do this. fn := func(label, value string) (newLabel, newValue string, err error) { return scope.Convertor.ConvertFieldLabel(scope.APIVersion, scope.Kind, label, value) } if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil { // TODO: allow bad request to set field causes based on query parameters err = errors.NewBadRequest(err.Error()) errorJSON(err, scope.Codec, w) return } if hasName { // metadata.name is the canonical internal name. // generic.SelectionPredicate will notice that this is // a request for a single object and optimize the // storage query accordingly. nameSelector := fields.OneTermEqualSelector("metadata.name", name) if opts.FieldSelector != nil && !opts.FieldSelector.Empty() { // It doesn't make sense to ask for both a name // and a field selector, since just the name is // sufficient to narrow down the request to a // single object. errorJSON( errors.NewBadRequest("both a name and a field selector provided; please provide one or the other."), scope.Codec, w, ) return } opts.FieldSelector = nameSelector } if (opts.Watch || forceWatch) && rw != nil { watcher, err := rw.Watch(ctx, opts.LabelSelector, opts.FieldSelector, opts.ResourceVersion) if err != nil { errorJSON(err, scope.Codec, w) return } serveWatch(watcher, scope, w, req, minRequestTimeout) return } result, err := r.List(ctx, opts.LabelSelector, opts.FieldSelector) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setListSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request) } }