Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/openvsx-proxy/pkg/modifyresponse.go
2498 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package pkg
6
7
import (
8
"bytes"
9
"fmt"
10
"io/ioutil"
11
"net/http"
12
"strconv"
13
"time"
14
"unicode/utf8"
15
16
"github.com/gitpod-io/gitpod/common-go/log"
17
"github.com/sirupsen/logrus"
18
)
19
20
func (o *OpenVSXProxy) ModifyResponse(r *http.Response) error {
21
reqid := r.Request.Context().Value(REQUEST_ID_CTX).(string)
22
key, ok := r.Request.Context().Value(REQUEST_CACHE_KEY_CTX).(string)
23
24
logFields := logrus.Fields{
25
LOG_FIELD_FUNC: "response_handler",
26
LOG_FIELD_REQUEST_ID: reqid,
27
LOG_FIELD_REQUEST: key,
28
LOG_FIELD_STATUS: strconv.Itoa(r.StatusCode),
29
"response_content_length": r.Header.Get("Content-Length"),
30
}
31
32
start := time.Now()
33
defer func(ts time.Time) {
34
duration := time.Since(ts)
35
o.metrics.DurationResponseProcessingHistogram.Observe(duration.Seconds())
36
log.
37
WithFields(logFields).
38
WithFields(o.DurationLogFields(duration)).
39
Info("processing response finished")
40
}(start)
41
42
log.WithFields(logFields).Debug("handling response")
43
o.metrics.IncStatusCounter(r.Request, strconv.Itoa(r.StatusCode))
44
45
if !ok {
46
return nil
47
}
48
49
if key == "" {
50
log.WithFields(logFields).Error("cache key header is missing - sending response as is")
51
return nil
52
}
53
54
rawBody, err := ioutil.ReadAll(r.Body)
55
if err != nil {
56
log.WithFields(logFields).WithError(err).Error("error reading response raw body")
57
return err
58
}
59
r.Body.Close()
60
61
if r.StatusCode >= 500 || r.StatusCode == http.StatusTooManyRequests || r.StatusCode == http.StatusRequestTimeout {
62
// use cache if exists
63
bodyLogField := "(binary)"
64
if utf8.Valid(rawBody) {
65
bodyStr := string(rawBody)
66
truncatedSuffix := ""
67
if len(bodyStr) > 500 {
68
truncatedSuffix = "... [truncated]"
69
}
70
bodyLogField = fmt.Sprintf("%.500s%s", bodyStr, truncatedSuffix)
71
}
72
log.
73
WithFields(logFields).
74
WithField("body", bodyLogField).
75
Warn("error from upstream server - trying to use cached response")
76
cached, ok, err := o.ReadCache(key)
77
if err != nil {
78
log.WithFields(logFields).WithError(err).Error("cannot read from cache")
79
return nil
80
}
81
if !ok {
82
log.WithFields(logFields).Debug("cache has no entry for key")
83
return nil
84
}
85
r.Header = cached.Header
86
if v := r.Header.Get("Access-Control-Allow-Origin"); v != "" && v != "*" {
87
r.Header.Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
88
}
89
r.Body = ioutil.NopCloser(bytes.NewBuffer(cached.Body))
90
r.ContentLength = int64(len(cached.Body))
91
r.StatusCode = cached.StatusCode
92
log.WithFields(logFields).Debug("used cache response due to an upstream error")
93
o.metrics.BackupCacheServeCounter.Inc()
94
return nil
95
}
96
97
// no error (status code < 500)
98
cacheObj := &CacheObject{
99
Header: r.Header,
100
Body: rawBody,
101
StatusCode: r.StatusCode,
102
}
103
err = o.StoreCache(key, cacheObj)
104
if err != nil {
105
log.WithFields(logFields).WithError(err).Error("error storing response to cache")
106
} else {
107
log.WithFields(logFields).Debug("successfully stored response to cache")
108
}
109
110
r.Body = ioutil.NopCloser(bytes.NewBuffer(rawBody))
111
r.ContentLength = int64(len(rawBody))
112
r.Header.Set("Content-Length", strconv.Itoa(len(rawBody)))
113
return nil
114
}
115
116