summaryrefslogtreecommitdiff
path: root/chromium/tools/chrome_proxy/testserver/server.go
blob: 0861fab3efd1f68246f1804a5ed5bbf8ac3a9320 (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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Test server to facilitate the data reduction proxy Telemetry tests.
//
// The server runs at http://chromeproxy-test.appspot.com/. Please contact
// people in OWNERS for server issues.
//
// For running an AppEngine Go server, see:
// https://developers.google.com/appengine/docs/go/gettingstarted/introduction.
//
// The goal is to keep the test logic on the client side (Telemetry)
// as much as possible. This server will only return a resource
// and/or override the response as specified by the data encoded
// in the request URL queries.
//
// For example, on receiving the query
// /default?respBody=bmV3IGJvZHk=&respHeader=eyJWaWEiOlsiVmlhMSIsIlZpYTIiXX0%3D&respStatus=204
// the server sends back a response with
//	Status code: 204
//	Additional response headers: "Via: Via1" and "Via: Via2"
//	Response body: "new body"
// where the overriding headers and body are base64 encoded in the request query.

package server

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

func init() {
	http.HandleFunc("/requestHeader", requestHeader)
	http.HandleFunc("/resource", resource)
	http.HandleFunc("/default", defaultResponse)
	http.HandleFunc("/blackhole", blackholeProxy)
}

// requestHander returns request headers in response body as text.
func requestHeader(w http.ResponseWriter, r *http.Request) {
	r.Header.Write(w)
}

// resource returns the content of a data file specified by "r=" query as the response body.
// The response could be overridden by request queries.
// See parseOverrideQuery.
func resource(w http.ResponseWriter, r *http.Request) {
	wroteBody, err := applyOverride(w, r)
	if err != nil || wroteBody {
		return
	}
	path, ok := r.URL.Query()["r"]
	if !ok || len(path) != 1 {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("no resource in query"))
		return
	}
	if _, err := writeFromFile(w, path[0]); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(fmt.Sprintf("Failed to get %s: %v", path[0], err)))
		return
	}
}

// defaultResponse returns "ok" as response body, if the body is not overridden.
// The response could be overridden by request queries.
// See parseOverrideQuery.
func defaultResponse(w http.ResponseWriter, r *http.Request) {
	wroteBody, err := applyOverride(w, r)
	if err != nil {
		return
	}
	if !wroteBody {
		w.Write([]byte("ok"))
	}
}

// blackholePoxy delays 90 seconds for proxied responses, in order to test if
// the proxy will timeout on the site. Responds immediately to any other request.
func blackholeProxy(w http.ResponseWriter, r *http.Request) {
	if strings.Contains(r.Header.Get("via"), "Chrome-Compression-Proxy") {
		// Causes timeout on proxy, will then send BLOCK_ONCE.
		// Appspot will 502 traffic at 120 seconds with no response.
		time.Sleep(90 * time.Second);
		w.Write([]byte("You are proxy"));
	} else {
		w.Write([]byte("You are direct"));
	}
}

type override struct {
	status int
	header http.Header
	body   io.Reader
}

// parseOverrideQuery parses the queries in r and returns an override.
// It supports the following queries:
//   "respStatus": an integer to override response status code;
//   "respHeader": base64 encoded JSON data to override the response headers;
//   "respBody": base64 encoded JSON data to override the response body.
func parseOverrideQuery(r *http.Request) (*override, error) {
	q := r.URL.Query()
	resp := &override{0, nil, nil}
	if v, ok := q["respStatus"]; ok && len(v) == 1 && len(v[0]) > 0 {
		status, err := strconv.ParseInt(v[0], 10, 0)
		if err != nil {
			return nil, errors.New(fmt.Sprintf("respStatus: %v", err))
		}
		resp.status = int(status)
	}
	if v, ok := q["respHeader"]; ok && len(v) == 1 && len(v[0]) > 0 {
		// Example header after base64 decoding:
		//  {"Via": ["Telemetry Test", "Test2"], "Name": ["XYZ"], "Cache-Control": ["public"]}
		headerValue, err := base64.URLEncoding.DecodeString(v[0])
		if err != nil {
			return nil, errors.New(fmt.Sprintf("Decoding respHeader: %v", err))
		}
		var header http.Header
		err = json.Unmarshal(headerValue, &header)
		if err != nil {
			return nil, errors.New(
				fmt.Sprintf("Unmarlshal (%s) error: %v", string(headerValue), err))
		}
		resp.header = header
	}
	if v, ok := q["respBody"]; ok && len(v) == 1 && len(v[0]) > 0 {
		body, err := base64.URLEncoding.DecodeString(v[0])
		if err != nil {
			return nil, errors.New(
				fmt.Sprintf("Decoding respBody error: %v", err))
		}
		resp.body = bytes.NewBuffer(body)
	}
	return resp, nil
}

// applyOverride applies the override queries in r to w and returns whether the response
// body is overridden.
func applyOverride(w http.ResponseWriter, r *http.Request) (wroteBody bool, err error) {
	resp, err := parseOverrideQuery(r)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(err.Error()))
		return false, err
	}
	headers := w.Header()
	if resp.header != nil {
		for k, v := range resp.header {
			headers[k] = v
		}
	}
	if resp.status > 0 {
		w.WriteHeader(resp.status)
	}
	if resp.body != nil {
		_, err := io.Copy(w, resp.body)
		return true, err
	}
	return false, nil
}

func writeFromFile(w io.Writer, filename string) (int64, error) {
	f, err := os.Open(filename)
	if err != nil {
		return 0, err
	}
	return io.Copy(w, f)
}