backendproxy.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package handler
  15. import (
  16. "context"
  17. "fmt"
  18. "net/http"
  19. "net/url"
  20. "strings"
  21. "time"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/appctx"
  24. "yunion.io/x/pkg/util/httputils"
  25. "yunion.io/x/onecloud/pkg/apis/identity"
  26. "yunion.io/x/onecloud/pkg/appsrv"
  27. "yunion.io/x/onecloud/pkg/httperrors"
  28. "yunion.io/x/onecloud/pkg/mcclient/auth"
  29. "yunion.io/x/onecloud/pkg/proxy"
  30. )
  31. type SBackendServiceProxyHandler struct {
  32. prefix string
  33. readWorker *appsrv.SWorkerManager
  34. writeWorker *appsrv.SWorkerManager
  35. }
  36. func NewBackendServiceProxyHandler(prefix string, readWorkerCount, writeWorkerCount int) *SBackendServiceProxyHandler {
  37. log.Infof("NewBackendServiceProxyHandler: %s, readWorkerCount: %d, writeWorkerCount: %d", prefix, readWorkerCount, writeWorkerCount)
  38. return &SBackendServiceProxyHandler{
  39. prefix: prefix,
  40. readWorker: appsrv.NewWorkerManager("apigateway-backend-api-read", readWorkerCount, appsrv.DEFAULT_BACKLOG, false),
  41. writeWorker: appsrv.NewWorkerManager("apigateway-backend-api-write", writeWorkerCount, appsrv.DEFAULT_BACKLOG, false),
  42. }
  43. }
  44. func removeLeadingSlash(p string) string {
  45. for len(p) > 0 && p[0] == '/' {
  46. p = p[1:]
  47. }
  48. return p
  49. }
  50. func (h *SBackendServiceProxyHandler) requestManipulator(ctx context.Context, r *http.Request) (*http.Request, error) {
  51. // remove leading prefixes /api/s/<service>
  52. path := r.URL.Path[len("/api/s/"):]
  53. path = removeLeadingSlash(path)
  54. slashPos := strings.Index(path, "/")
  55. if slashPos <= 0 {
  56. return r, httperrors.NewBadRequestError("invalid request URL %s", r.URL.Path)
  57. }
  58. path = path[slashPos:]
  59. if strings.HasPrefix(path, "/r/") {
  60. path = path[len("/r/"):]
  61. path = removeLeadingSlash(path)
  62. slashPos := strings.Index(path, "/")
  63. if slashPos <= 0 {
  64. return r, httperrors.NewBadRequestError("invalid request URL %s", r.URL.Path)
  65. }
  66. path = path[slashPos:]
  67. if strings.HasPrefix(path, "/z/") {
  68. path = path[len("/z/"):]
  69. path = removeLeadingSlash(path)
  70. slashPos := strings.Index(path, "/")
  71. if slashPos <= 0 {
  72. return r, httperrors.NewBadRequestError("invalid request URL %s", r.URL.Path)
  73. }
  74. path = path[slashPos:]
  75. }
  76. }
  77. log.Debugf("Path: %s => %s", r.URL.Path, path)
  78. r.URL = &url.URL{
  79. Path: path,
  80. RawQuery: r.URL.RawQuery,
  81. Fragment: r.URL.Fragment,
  82. }
  83. return r, nil
  84. }
  85. func (h *SBackendServiceProxyHandler) Bind(app *appsrv.Application) {
  86. app.AddReverseProxyHandlerWithCallbackConfig(
  87. h.prefix, h.fetchReverseEndpoint(), h.requestManipulator,
  88. func(method string, hi *appsrv.SHandlerInfo) *appsrv.SHandlerInfo {
  89. if method == "OPTIONS" {
  90. return nil
  91. }
  92. if method == "GET" || method == "PUT" || method == "POST" {
  93. hi.SetProcessTimeout(6 * time.Hour)
  94. }
  95. var worker *appsrv.SWorkerManager
  96. if method == "GET" || method == "HEAD" {
  97. worker = h.readWorker
  98. } else {
  99. worker = h.writeWorker
  100. }
  101. hi.SetWorkerManager(worker)
  102. return hi
  103. },
  104. )
  105. }
  106. func (h *SBackendServiceProxyHandler) fetchReverseEndpoint() *proxy.SEndpointFactory {
  107. f := func(ctx context.Context, r *http.Request) (string, error) {
  108. params := appctx.AppContextParams(ctx)
  109. serviceName := params["<service>"]
  110. if len(serviceName) == 0 {
  111. return "", httperrors.NewBadRequestError("no service")
  112. }
  113. path := r.URL.Path
  114. serviceSeg := fmt.Sprintf("/api/s/%s/", serviceName)
  115. pos := strings.Index(path, serviceSeg)
  116. if pos < 0 {
  117. return "", httperrors.NewBadRequestError("malformed URL, expect service")
  118. }
  119. path = path[pos+len(serviceSeg):]
  120. path = removeLeadingSlash(path)
  121. region := FetchRegion(r)
  122. zone := ""
  123. if strings.HasPrefix(path, "r/") {
  124. path = path[len("r/"):]
  125. path = removeLeadingSlash(path)
  126. slashPos := strings.Index(path, "/")
  127. if slashPos <= 0 {
  128. return "", httperrors.NewBadRequestError("malformed URL, expect region")
  129. }
  130. region = path[:slashPos]
  131. path = path[slashPos+1:]
  132. if strings.HasPrefix(path, "z/") {
  133. path = path[len("z/"):]
  134. path = removeLeadingSlash(path)
  135. slashPos := strings.Index(path, "/")
  136. if slashPos <= 0 {
  137. return "", httperrors.NewBadRequestError("malformed URL, expect zone")
  138. }
  139. zone = path[:slashPos]
  140. }
  141. }
  142. session := auth.GetAdminSession(ctx, region)
  143. if len(zone) > 0 {
  144. session.SetZone(zone)
  145. }
  146. ep, err := session.GetServiceURL(serviceName, identity.EndpointInterfaceInternal, httputils.THttpMethod(r.Method))
  147. if err != nil {
  148. return "", httperrors.NewBadRequestError("invalid service %s: %s", serviceName, err)
  149. }
  150. return getEndpointSchemeHost(ep)
  151. }
  152. return proxy.NewEndpointFactory(f, "backendService")
  153. }