reflect.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. // A web app for Google App Engine that proxies HTTP requests and responses to
  2. // the Snowflake broker.
  3. package reflect
  4. import (
  5. "context"
  6. "io"
  7. "net/http"
  8. "net/url"
  9. "time"
  10. "google.golang.org/appengine"
  11. "google.golang.org/appengine/log"
  12. "google.golang.org/appengine/urlfetch"
  13. )
  14. const (
  15. forwardURL = "https://snowflake-broker.bamsoftware.com/"
  16. // A timeout of 0 means to use the App Engine default (5 seconds).
  17. urlFetchTimeout = 20 * time.Second
  18. )
  19. var ctx context.Context
  20. // Join two URL paths.
  21. func pathJoin(a, b string) string {
  22. if len(a) > 0 && a[len(a)-1] == '/' {
  23. a = a[:len(a)-1]
  24. }
  25. if len(b) == 0 || b[0] != '/' {
  26. b = "/" + b
  27. }
  28. return a + b
  29. }
  30. // We reflect only a whitelisted set of header fields. Otherwise, we may copy
  31. // headers like Transfer-Encoding that interfere with App Engine's own
  32. // hop-by-hop headers.
  33. var reflectedHeaderFields = []string{
  34. "Content-Type",
  35. "X-Session-Id",
  36. }
  37. // Make a copy of r, with the URL being changed to be relative to forwardURL,
  38. // and including only the headers in reflectedHeaderFields.
  39. func copyRequest(r *http.Request) (*http.Request, error) {
  40. u, err := url.Parse(forwardURL)
  41. if err != nil {
  42. return nil, err
  43. }
  44. // Append the requested path to the path in forwardURL, so that
  45. // forwardURL can be something like "https://example.com/reflect".
  46. u.Path = pathJoin(u.Path, r.URL.Path)
  47. c, err := http.NewRequest(r.Method, u.String(), r.Body)
  48. if err != nil {
  49. return nil, err
  50. }
  51. for _, key := range reflectedHeaderFields {
  52. values, ok := r.Header[key]
  53. if ok {
  54. for _, value := range values {
  55. c.Header.Add(key, value)
  56. }
  57. }
  58. }
  59. return c, nil
  60. }
  61. func handler(w http.ResponseWriter, r *http.Request) {
  62. ctx = appengine.NewContext(r)
  63. fr, err := copyRequest(r)
  64. if err != nil {
  65. log.Errorf(ctx, "copyRequest: %s", err)
  66. http.Error(w, err.Error(), http.StatusInternalServerError)
  67. return
  68. }
  69. if urlFetchTimeout != 0 {
  70. var cancel context.CancelFunc
  71. ctx, cancel = context.WithTimeout(ctx, urlFetchTimeout)
  72. defer cancel()
  73. }
  74. // Use urlfetch.Transport directly instead of urlfetch.Client because we
  75. // want only a single HTTP transaction, not following redirects.
  76. transport := urlfetch.Transport{
  77. Context: ctx,
  78. }
  79. resp, err := transport.RoundTrip(fr)
  80. if err != nil {
  81. log.Errorf(ctx, "RoundTrip: %s", err)
  82. http.Error(w, err.Error(), http.StatusInternalServerError)
  83. return
  84. }
  85. defer resp.Body.Close()
  86. for _, key := range reflectedHeaderFields {
  87. values, ok := resp.Header[key]
  88. if ok {
  89. for _, value := range values {
  90. w.Header().Add(key, value)
  91. }
  92. }
  93. }
  94. w.WriteHeader(resp.StatusCode)
  95. n, err := io.Copy(w, resp.Body)
  96. if err != nil {
  97. log.Errorf(ctx, "io.Copy after %d bytes: %s", n, err)
  98. }
  99. }
  100. func init() {
  101. http.HandleFunc("/", handler)
  102. }