k8s.io/apiserver@v0.31.1/pkg/endpoints/discovery/aggregated/etag.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package aggregated
    18  
    19  import (
    20  	"crypto/sha512"
    21  	"encoding/json"
    22  	"fmt"
    23  	"net/http"
    24  	"strconv"
    25  
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  	"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
    29  )
    30  
    31  // This file exposes helper functions used for calculating the E-Tag header
    32  // used in discovery endpoint responses
    33  
    34  // Attaches Cache-Busting functionality to an endpoint
    35  //   - Sets ETag header to provided hash
    36  //   - Replies with 304 Not Modified, if If-None-Match header matches hash
    37  //
    38  // hash should be the value of calculateETag on object. If hash is empty, then
    39  // the object is simply serialized without E-Tag functionality
    40  func ServeHTTPWithETag(
    41  	object runtime.Object,
    42  	hash string,
    43  	targetGV schema.GroupVersion,
    44  	serializer runtime.NegotiatedSerializer,
    45  	w http.ResponseWriter,
    46  	req *http.Request,
    47  ) {
    48  	// ETag must be enclosed in double quotes:
    49  	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
    50  	quotedHash := strconv.Quote(hash)
    51  	w.Header().Set("ETag", quotedHash)
    52  	w.Header().Set("Vary", "Accept")
    53  	w.Header().Set("Cache-Control", "public")
    54  
    55  	// If Request includes If-None-Match and matches hash, reply with 304
    56  	// Otherwise, we delegate to the handler for actual content
    57  	//
    58  	// According to documentation, An Etag within an If-None-Match
    59  	// header will be enclosed within double quotes:
    60  	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
    61  	if clientCachedHash := req.Header.Get("If-None-Match"); quotedHash == clientCachedHash {
    62  		w.WriteHeader(http.StatusNotModified)
    63  		return
    64  	}
    65  
    66  	responsewriters.WriteObjectNegotiated(
    67  		serializer,
    68  		DiscoveryEndpointRestrictions,
    69  		targetGV,
    70  		w,
    71  		req,
    72  		http.StatusOK,
    73  		object,
    74  		true,
    75  	)
    76  }
    77  
    78  func calculateETag(resources interface{}) (string, error) {
    79  	serialized, err := json.Marshal(resources)
    80  	if err != nil {
    81  		return "", err
    82  	}
    83  
    84  	return fmt.Sprintf("%X", sha512.Sum512(serialized)), nil
    85  }