amp.go 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. package main
  2. import (
  3. "log"
  4. "net/http"
  5. "strings"
  6. "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/amp"
  7. "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages"
  8. "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util"
  9. )
  10. // ampClientOffers is the AMP-speaking endpoint for client poll messages,
  11. // intended for access via an AMP cache. In contrast to the other clientOffers,
  12. // the client's encoded poll message is stored in the URL path rather than the
  13. // HTTP request body (because an AMP cache does not support POST), and the
  14. // encoded client poll response is sent back as AMP-armored HTML.
  15. func ampClientOffers(i *IPC, w http.ResponseWriter, r *http.Request) {
  16. // The encoded client poll message immediately follows the /amp/client/
  17. // path prefix, so this function unfortunately needs to be aware of and
  18. // remote its own routing prefix.
  19. path := strings.TrimPrefix(r.URL.Path, "/amp/client/")
  20. if path == r.URL.Path {
  21. // The path didn't start with the expected prefix. This probably
  22. // indicates an internal bug.
  23. log.Println("ampClientOffers: unexpected prefix in path")
  24. w.WriteHeader(http.StatusInternalServerError)
  25. return
  26. }
  27. var encPollReq []byte
  28. var response []byte
  29. var err error
  30. encPollReq, err = amp.DecodePath(path)
  31. if err == nil {
  32. arg := messages.Arg{
  33. Body: encPollReq,
  34. RemoteAddr: util.GetClientIp(r),
  35. RendezvousMethod: messages.RendezvousAmpCache,
  36. }
  37. err = i.ClientOffers(arg, &response)
  38. } else {
  39. response, err = (&messages.ClientPollResponse{
  40. Error: "cannot decode URL path",
  41. }).EncodePollResponse()
  42. }
  43. if err != nil {
  44. // We couldn't even construct a JSON object containing an error
  45. // message :( Nothing to do but signal an error at the HTTP
  46. // layer. The AMP cache will translate this 500 status into a
  47. // 404 status.
  48. // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#redirect-%26-error-handling
  49. log.Printf("ampClientOffers: %v", err)
  50. w.WriteHeader(http.StatusInternalServerError)
  51. return
  52. }
  53. w.Header().Set("Content-Type", "text/html")
  54. // Attempt to hint to an AMP cache not to waste resources caching this
  55. // document. "The Google AMP Cache considers any document fresh for at
  56. // least 15 seconds."
  57. // https://developers.google.com/amp/cache/overview#google-amp-cache-updates
  58. w.Header().Set("Cache-Control", "max-age=15")
  59. w.WriteHeader(http.StatusOK)
  60. enc, err := amp.NewArmorEncoder(w)
  61. if err != nil {
  62. log.Printf("amp.NewArmorEncoder: %v", err)
  63. return
  64. }
  65. defer enc.Close()
  66. if _, err := enc.Write(response); err != nil {
  67. log.Printf("ampClientOffers: unable to write answer: %v", err)
  68. }
  69. }