Welcome to mirror list, hosted at ThFree Co, Russian Federation.

artifact.go « artifact « internal - gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: bcb525ac5e5a62441315a854cc7f2cb5765ecf33 (plain)
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)))
}