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 buildservice
import (
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"regexp"
"strings"
"time"
"gitlab.com/gitlab-org/gitlab-pages/internal/httptransport"
)
const (
proxyURLTemplate = "%s/api/v4/jobs/%s/proxy/%s/%s"
domainKeyword = "proxy"
)
// The domain structure is:
// username.domainKeywork.build_id.service_name.service_port.pages.domain
// Ex: root.proxy.1.service_name.service_port.xip.io:3010
// Service_port can be either a number or a string
var (
// Captures subgroup + project, job ID and artifacts path
domainExtractor = regexp.MustCompile(`(?i)\A(.+)\.` + domainKeyword + `\.(\d+)\.(.+)\.(.+)\z`)
)
// Artifact proxies requests for artifact files to the GitLab artifacts API
type BuildService struct {
server string
suffix string
client *http.Client
}
// New when provided the arguments defined herein, returns a pointer to an
// BuildService that is used to proxy requests.
func New(server string, timeoutSeconds int, pagesDomain string) *BuildService {
return &BuildService{
server: strings.TrimRight(server, "/"),
suffix: "." + strings.ToLower(pagesDomain),
client: &http.Client{
Timeout: time.Second * time.Duration(timeoutSeconds),
Transport: httptransport.Transport,
},
}
}
// 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 *BuildService) TryMakeRequest(host string, token string, w http.ResponseWriter, r *http.Request) bool {
if a.server == "" || host == "" || token == "" {
return false
}
rewrittenReq, ok := a.rewriteRequest(host, r)
if !ok {
return false
}
a.makeRequest(w, rewrittenReq, token)
return true
}
func (a *BuildService) makeRequest(w http.ResponseWriter, req *http.Request, token string) {
u, _ := url.Parse(a.server)
proxy := httputil.NewSingleHostReverseProxy(u)
req.Header.Add("Authorization", "Bearer "+token)
proxy.ServeHTTP(w, req)
}
// 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.
//
// The URL is generated from the host (which contains the top-level group and
// ends with the pagesDomain) and the path (which contains any subgroups, the
// project, a job ID
func (a *BuildService) rewriteRequest(host string, r *http.Request) (*http.Request, bool) {
if !strings.HasSuffix(strings.ToLower(host), a.suffix) {
return nil, false
}
topDomain := host[0 : len(host)-len(a.suffix)]
domainParts := domainExtractor.FindAllStringSubmatch(topDomain, 1)
if len(domainParts) != 1 || len(domainParts[0]) != 5 {
return nil, false
}
jobID := domainParts[0][2]
serviceName := domainParts[0][3]
servicePort := domainParts[0][4]
generated := fmt.Sprintf(proxyURLTemplate, a.server, jobID, serviceName, servicePort)
u, err := url.Parse(generated)
if err != nil {
return nil, false
}
log.Printf("r.URL.RawQuery: %#+v\n", r.URL.RawQuery)
q := url.Values{"path": []string{r.URL.Path}}
q["wrapped_raw_query_"+jobID] = []string{r.URL.RawQuery}
u.RawQuery = q.Encode()
// Rewritten request
r.URL = u
log.Printf("r.URL.Pathj: %#+v\n", r.URL.Path)
log.Printf("r.URL.Query(): %#+v\n", r.URL.Query())
// if err := r.ParseForm(); err != nil {
// return nil, false
// }
// r.Form.Set("HTTP_JOB_TOKEN", "2z3OV406fjsxgAF3xUjS0JA/+u9sVpr0vKhU0FuLRwquFJx5")
return r, true
}
|