You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

deliver.go 5.8 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package webhook
  5. import (
  6. "crypto/tls"
  7. "fmt"
  8. "io/ioutil"
  9. "net"
  10. "net/http"
  11. "net/url"
  12. "strings"
  13. "sync"
  14. "time"
  15. "code.gitea.io/gitea/models"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/setting"
  18. "github.com/gobwas/glob"
  19. "github.com/unknwon/com"
  20. )
  21. // Deliver deliver hook task
  22. func Deliver(t *models.HookTask) error {
  23. t.IsDelivered = true
  24. var req *http.Request
  25. var err error
  26. switch t.HTTPMethod {
  27. case "":
  28. log.Info("HTTP Method for webhook %d empty, setting to POST as default", t.ID)
  29. fallthrough
  30. case http.MethodPost:
  31. switch t.ContentType {
  32. case models.ContentTypeJSON:
  33. req, err = http.NewRequest("POST", t.URL, strings.NewReader(t.PayloadContent))
  34. if err != nil {
  35. return err
  36. }
  37. req.Header.Set("Content-Type", "application/json")
  38. case models.ContentTypeForm:
  39. var forms = url.Values{
  40. "payload": []string{t.PayloadContent},
  41. }
  42. req, err = http.NewRequest("POST", t.URL, strings.NewReader(forms.Encode()))
  43. if err != nil {
  44. return err
  45. }
  46. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  47. }
  48. case http.MethodGet:
  49. u, err := url.Parse(t.URL)
  50. if err != nil {
  51. return err
  52. }
  53. vals := u.Query()
  54. vals["payload"] = []string{t.PayloadContent}
  55. u.RawQuery = vals.Encode()
  56. req, err = http.NewRequest("GET", u.String(), nil)
  57. if err != nil {
  58. return err
  59. }
  60. default:
  61. return fmt.Errorf("Invalid http method for webhook: [%d] %v", t.ID, t.HTTPMethod)
  62. }
  63. req.Header.Add("X-Gitea-Delivery", t.UUID)
  64. req.Header.Add("X-Gitea-Event", string(t.EventType))
  65. req.Header.Add("X-Gitea-Signature", t.Signature)
  66. req.Header.Add("X-Gogs-Delivery", t.UUID)
  67. req.Header.Add("X-Gogs-Event", string(t.EventType))
  68. req.Header.Add("X-Gogs-Signature", t.Signature)
  69. req.Header["X-GitHub-Delivery"] = []string{t.UUID}
  70. req.Header["X-GitHub-Event"] = []string{string(t.EventType)}
  71. // Record delivery information.
  72. t.RequestInfo = &models.HookRequest{
  73. Headers: map[string]string{},
  74. }
  75. for k, vals := range req.Header {
  76. t.RequestInfo.Headers[k] = strings.Join(vals, ",")
  77. }
  78. t.ResponseInfo = &models.HookResponse{
  79. Headers: map[string]string{},
  80. }
  81. defer func() {
  82. t.Delivered = time.Now().UnixNano()
  83. if t.IsSucceed {
  84. log.Trace("Hook delivered: %s", t.UUID)
  85. } else {
  86. log.Trace("Hook delivery failed: %s", t.UUID)
  87. }
  88. if err := models.UpdateHookTask(t); err != nil {
  89. log.Error("UpdateHookTask [%d]: %v", t.ID, err)
  90. }
  91. // Update webhook last delivery status.
  92. w, err := models.GetWebhookByID(t.HookID)
  93. if err != nil {
  94. log.Error("GetWebhookByID: %v", err)
  95. return
  96. }
  97. if t.IsSucceed {
  98. w.LastStatus = models.HookStatusSucceed
  99. } else {
  100. w.LastStatus = models.HookStatusFail
  101. }
  102. if err = models.UpdateWebhookLastStatus(w); err != nil {
  103. log.Error("UpdateWebhookLastStatus: %v", err)
  104. return
  105. }
  106. }()
  107. resp, err := webhookHTTPClient.Do(req)
  108. if err != nil {
  109. t.ResponseInfo.Body = fmt.Sprintf("Delivery: %v", err)
  110. return err
  111. }
  112. defer resp.Body.Close()
  113. // Status code is 20x can be seen as succeed.
  114. t.IsSucceed = resp.StatusCode/100 == 2
  115. t.ResponseInfo.Status = resp.StatusCode
  116. for k, vals := range resp.Header {
  117. t.ResponseInfo.Headers[k] = strings.Join(vals, ",")
  118. }
  119. p, err := ioutil.ReadAll(resp.Body)
  120. if err != nil {
  121. t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err)
  122. return err
  123. }
  124. t.ResponseInfo.Body = string(p)
  125. return nil
  126. }
  127. // DeliverHooks checks and delivers undelivered hooks.
  128. // TODO: shoot more hooks at same time.
  129. func DeliverHooks() {
  130. tasks, err := models.FindUndeliveredHookTasks()
  131. if err != nil {
  132. log.Error("DeliverHooks: %v", err)
  133. return
  134. }
  135. // Update hook task status.
  136. for _, t := range tasks {
  137. if err = Deliver(t); err != nil {
  138. log.Error("deliver: %v", err)
  139. }
  140. }
  141. // Start listening on new hook requests.
  142. for repoIDStr := range hookQueue.Queue() {
  143. log.Trace("DeliverHooks [repo_id: %v]", repoIDStr)
  144. hookQueue.Remove(repoIDStr)
  145. repoID, err := com.StrTo(repoIDStr).Int64()
  146. if err != nil {
  147. log.Error("Invalid repo ID: %s", repoIDStr)
  148. continue
  149. }
  150. tasks, err := models.FindRepoUndeliveredHookTasks(repoID)
  151. if err != nil {
  152. log.Error("Get repository [%d] hook tasks: %v", repoID, err)
  153. continue
  154. }
  155. for _, t := range tasks {
  156. if err = Deliver(t); err != nil {
  157. log.Error("deliver: %v", err)
  158. }
  159. }
  160. }
  161. }
  162. var (
  163. webhookHTTPClient *http.Client
  164. once sync.Once
  165. hostMatchers []glob.Glob
  166. )
  167. func webhookProxy() func(req *http.Request) (*url.URL, error) {
  168. if setting.Webhook.ProxyURL == "" {
  169. return http.ProxyFromEnvironment
  170. }
  171. once.Do(func() {
  172. for _, h := range setting.Webhook.ProxyHosts {
  173. if g, err := glob.Compile(h); err == nil {
  174. hostMatchers = append(hostMatchers, g)
  175. } else {
  176. log.Error("glob.Compile %s failed: %v", h, err)
  177. }
  178. }
  179. })
  180. return func(req *http.Request) (*url.URL, error) {
  181. for _, v := range hostMatchers {
  182. if v.Match(req.URL.Host) {
  183. return http.ProxyURL(setting.Webhook.ProxyURLFixed)(req)
  184. }
  185. }
  186. return http.ProxyFromEnvironment(req)
  187. }
  188. }
  189. // InitDeliverHooks starts the hooks delivery thread
  190. func InitDeliverHooks() {
  191. timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
  192. webhookHTTPClient = &http.Client{
  193. Transport: &http.Transport{
  194. TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify},
  195. Proxy: webhookProxy(),
  196. Dial: func(netw, addr string) (net.Conn, error) {
  197. conn, err := net.DialTimeout(netw, addr, timeout)
  198. if err != nil {
  199. return nil, err
  200. }
  201. return conn, conn.SetDeadline(time.Now().Add(timeout))
  202. },
  203. },
  204. }
  205. go DeliverHooks()
  206. }