aboutsummaryrefslogtreecommitdiff
path: root/broker/amp.go
blob: 4c7a036c7b5a3deac6b08557b010c0d6a76e610f (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
package main

import (
	"log"
	"net/http"
	"strings"

	"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/amp"
	"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages"
)

// ampClientOffers is the AMP-speaking endpoint for client poll messages,
// intended for access via an AMP cache. In contrast to the other clientOffers,
// the client's encoded poll message is stored in the URL path rather than the
// HTTP request body (because an AMP cache does not support POST), and the
// encoded client poll response is sent back as AMP-armored HTML.
func ampClientOffers(i *IPC, w http.ResponseWriter, r *http.Request) {
	// The encoded client poll message immediately follows the /amp/client/
	// path prefix, so this function unfortunately needs to be aware of and
	// remote its own routing prefix.
	path := strings.TrimPrefix(r.URL.Path, "/amp/client/")
	if path == r.URL.Path {
		// The path didn't start with the expected prefix. This probably
		// indicates an internal bug.
		log.Println("ampClientOffers: unexpected prefix in path")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	var encPollReq []byte
	var response []byte
	var err error

	encPollReq, err = amp.DecodePath(path)
	if err == nil {
		arg := messages.Arg{
			Body:             encPollReq,
			RemoteAddr:       "",
			RendezvousMethod: messages.RendezvousAmpCache,
		}
		err = i.ClientOffers(arg, &response)
	} else {
		response, err = (&messages.ClientPollResponse{
			Error: "cannot decode URL path",
		}).EncodePollResponse()
	}

	if err != nil {
		// We couldn't even construct a JSON object containing an error
		// message :( Nothing to do but signal an error at the HTTP
		// layer. The AMP cache will translate this 500 status into a
		// 404 status.
		// https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#redirect-%26-error-handling
		log.Printf("ampClientOffers: %v", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "text/html")
	// Attempt to hint to an AMP cache not to waste resources caching this
	// document. "The Google AMP Cache considers any document fresh for at
	// least 15 seconds."
	// https://developers.google.com/amp/cache/overview#google-amp-cache-updates
	w.Header().Set("Cache-Control", "max-age=15")
	w.WriteHeader(http.StatusOK)

	enc, err := amp.NewArmorEncoder(w)
	if err != nil {
		log.Printf("amp.NewArmorEncoder: %v", err)
		return
	}
	defer enc.Close()

	if _, err := enc.Write(response); err != nil {
		log.Printf("ampClientOffers: unable to write answer: %v", err)
	}
}