GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

handlers.go 4.49 KB
Newer Older
1 2 3 4 5 6 7 8 9
package main

import (
	"encoding/json"
	"fmt"
	"golang.org/x/time/rate"
	"io/ioutil"
	"log"
	"net/http"
10
	"path"
11
	"sync"
12 13 14
	"time"
)

15 16 17
var IndexPage string
var SuccessPage string
var FailurePage string
18
var reqChan = make(chan *TestRequest, 1000)
19 20 21 22 23

// TestResult represents the result of a test, sent back to the client as JSON
// object.
type TestResult struct {
	Functional bool    `json:"functional"`
24
	Error      string  `json:"error,omitempty"`
25 26 27
	Time       float64 `json:"time"`
}

28 29
type TestRequest struct {
	BridgeLine string `json:"bridge_line"`
30
	respChan   chan *TestResult
31 32
}

33 34 35 36
// limiter implements a rate limiter.  We allow 1 request per second on average
// with bursts of up to 5 requests per second.
var limiter = rate.NewLimiter(1, 5)

37 38 39 40 41 42 43 44
// LoadHtmlTemplates loads all HTML templates from the given directory.
func LoadHtmlTemplates(dir string) {

	IndexPage = LoadHtmlTemplate(path.Join(dir, "index.html"))
	SuccessPage = LoadHtmlTemplate(path.Join(dir, "success.html"))
	FailurePage = LoadHtmlTemplate(path.Join(dir, "failure.html"))
}

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
// LoadHtmlTemplate reads the content of the given filename and returns it as
// string.  If the function is unable to read the file, it logs a fatal error.
func LoadHtmlTemplate(filename string) string {

	content, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatal(err)
	}
	return string(content)
}

func SendResponse(w http.ResponseWriter, response string) {
	w.WriteHeader(http.StatusOK)
	fmt.Fprintln(w, response)
}

func SendHtmlResponse(w http.ResponseWriter, response string) {

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	SendResponse(w, response)
}

func SendJSONResponse(w http.ResponseWriter, response string) {

	w.Header().Set("Content-Type", "application/json")
70
	log.Printf("Test result: %s", response)
71 72 73 74 75 76 77 78
	SendResponse(w, response)
}

func Index(w http.ResponseWriter, r *http.Request) {

	SendHtmlResponse(w, IndexPage)
}

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
func dispatchRequests(shutdown chan bool, wg *sync.WaitGroup, numSeconds int) {

	log.Printf("Starting request dispatcher.")
	delay := time.Tick(time.Second * time.Duration(numSeconds))
	wg.Add(1)
	defer wg.Done()
	defer log.Printf("Stopping request dispatcher.")

	for {
		select {
		case <-shutdown:
			return
		case req := <-reqChan:
			log.Printf("Fetching next request; %d requests remain buffered.", len(reqChan))
			go processRequest(req)

			// Either wait for the delay to expire or the service to shut down;
			// whatever comes first.
			select {
			case <-shutdown:
				return
			case <-delay:
			}
		}
	}
}

func processRequest(req *TestRequest) {
107

108 109
	start := time.Now()
	err := bootstrapTorOverBridge(req.BridgeLine)
110 111 112 113 114 115 116 117
	end := time.Now()
	result := &TestResult{
		Functional: err == nil,
		Error:      "",
		Time:       float64(end.Sub(start).Milliseconds()) / 1000}
	if err != nil {
		result.Error = err.Error()
	}
118
	req.respChan <- result
119 120
}

121
func BridgeState(w http.ResponseWriter, r *http.Request) {
122

123 124 125 126 127 128 129 130 131 132 133 134 135
	b, err := ioutil.ReadAll(r.Body)
	defer r.Body.Close()
	if err != nil {
		log.Printf("Failed to read HTTP body: %s", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	req := &TestRequest{}
	if err := json.Unmarshal(b, &req); err != nil {
		log.Printf("Failed to unmarshal HTTP body %q: %s", b, err)
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
136 137
	}

138 139 140 141 142
	if req.BridgeLine == "" {
		log.Printf("Got request with empty bridge line.")
		http.Error(w, "no bridge line given", http.StatusBadRequest)
		return
	}
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164

	var res *TestResult
	// Do we have the given bridge line cached?  If so, we can respond right
	// away.
	if entry := cache.IsCached(req.BridgeLine); entry != nil {
		res = &TestResult{
			Functional: entry.Error == "",
			Error:      entry.Error,
			Time:       0.0}
	} else {
		respChan := make(chan *TestResult)
		req.respChan = respChan
		reqChan <- req
		res = <-respChan
	}

	jsonResult, err := json.Marshal(res)
	if err != nil {
		log.Printf("Bug: %s", err)
		http.Error(w, "failed to marshal test tesult", http.StatusInternalServerError)
	}
	SendJSONResponse(w, string(jsonResult))
165 166 167 168 169 170 171 172 173 174 175
}

func BridgeStateWeb(w http.ResponseWriter, r *http.Request) {

	r.ParseForm()
	// Rate-limit Web requests to prevent someone from abusing this service
	// as a port scanner.
	if limiter.Allow() == false {
		SendHtmlResponse(w, "Rate limit exceeded.")
		return
	}
176 177
	bridgeLine := r.Form.Get("bridge_line")
	if bridgeLine == "" {
178
		SendHtmlResponse(w, "No bridge line given.")
179 180
		return
	}
181 182
	if err := bootstrapTorOverBridge(bridgeLine); err == nil {
		SendHtmlResponse(w, SuccessPage)
183
	} else {
184
		SendHtmlResponse(w, FailurePage)
185 186
	}
}