1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
package artifact
import (
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"gitlab.com/gitlab-org/gitlab-pages/internal/httperrors"
)
const (
baseURL = "/projects/%s/jobs/%s/artifacts"
hostPatternTemplate = `(?i)\Aartifact~(\d+)~(\d+)\.%s\z`
minStatusCode = 200
maxStatusCode = 299
)
// Artifact is a struct that is made up of a url.URL, http.Client, and
// regexp.Regexp that is used to proxy requests where applicable.
type Artifact struct {
server string
client *http.Client
pattern *regexp.Regexp
}
// New when provided the arguments defined herein, returns a pointer to an
// Artifact that is used to proxy requests.
func New(s string, timeout int, pagesDomain string) *Artifact {
return &Artifact{
server: s,
client: &http.Client{Timeout: time.Second * time.Duration(timeout)},
pattern: hostPatternGen(pagesDomain),
}
}
// TryMakeRequest will attempt to proxy a request and write it to the argument
// http.ResponseWriter, ultimately returning a bool that indicates if the
// http.ResponseWriter has been written to in any capacity.
func (a *Artifact) TryMakeRequest(host string, w http.ResponseWriter, r *http.Request) bool {
if a == nil || a.server == "" {
return false
}
reqURL, ok := a.buildURL(host, r.URL.Path)
if !ok {
return false
}
resp, err := a.client.Get(reqURL.String())
if err != nil {
httperrors.Serve502(w)
return true
}
if resp.StatusCode == http.StatusNotFound {
httperrors.Serve404(w)
return true
}
if resp.StatusCode == http.StatusInternalServerError {
httperrors.Serve500(w)
return true
}
// we only cache responses within the 2xx series response codes
if (resp.StatusCode >= minStatusCode) && (resp.StatusCode <= maxStatusCode) {
w.Header().Set("Cache-Control", "max-age=3600")
}
w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
w.Header().Set("Content-Length", strconv.FormatInt(resp.ContentLength, 10))
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
return true
}
// buildURL returns a pointer to a url.URL for where the request should be
// proxied to. The returned bool will indicate if there is some sort of issue
// with the url while it is being generated.
func (a *Artifact) buildURL(host, path string) (*url.URL, bool) {
ids := a.pattern.FindAllStringSubmatch(host, -1)
if len(ids) != 1 || len(ids[0]) != 3 {
return nil, false
}
strippedIds := ids[0][1:3]
body := fmt.Sprintf(baseURL, strippedIds[0], strippedIds[1])
ourPath := a.server
if strings.HasSuffix(ourPath, "/") {
ourPath = ourPath[0:len(ourPath)-1] + body
} else {
ourPath = ourPath + body
}
if len(path) == 0 || strings.HasPrefix(path, "/") {
ourPath = ourPath + path
} else {
ourPath = ourPath + "/" + path
}
u, err := url.Parse(ourPath)
if err != nil {
return nil, false
}
return u, true
}
// hostPatternGen returns a pointer to a regexp.Regexp that is made up of
// the constant hostPatternTemplate and the argument which represents the pages domain.
// This is used to ensure that the requested page meets not only the hostPatternTemplate
// requirements, but is suffixed with the proper pagesDomain.
func hostPatternGen(pagesDomain string) *regexp.Regexp {
return regexp.MustCompile(fmt.Sprintf(hostPatternTemplate, regexp.QuoteMeta(pagesDomain)))
}
|