mirror of
https://github.com/matrix-org/dendrite
synced 2024-11-16 06:41:06 +01:00
232aef016c
This allows us in almost all places to use regions to further trace down long running tasks. Also removes an unused function.
349 lines
9.4 KiB
Go
349 lines
9.4 KiB
Go
// Copyright 2018 New Vector Ltd
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package query handles requests from other internal dendrite components when
|
|
// they interact with the AppServiceQueryAPI.
|
|
package query
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/matrix-org/dendrite/appservice/api"
|
|
"github.com/matrix-org/dendrite/internal"
|
|
"github.com/matrix-org/dendrite/setup/config"
|
|
)
|
|
|
|
const roomAliasExistsPath = "/rooms/"
|
|
const userIDExistsPath = "/users/"
|
|
|
|
// AppServiceQueryAPI is an implementation of api.AppServiceQueryAPI
|
|
type AppServiceQueryAPI struct {
|
|
HTTPClient *http.Client
|
|
Cfg *config.AppServiceAPI
|
|
ProtocolCache map[string]api.ASProtocolResponse
|
|
CacheMu sync.Mutex
|
|
}
|
|
|
|
// RoomAliasExists performs a request to '/room/{roomAlias}' on all known
|
|
// handling application services until one admits to owning the room
|
|
func (a *AppServiceQueryAPI) RoomAliasExists(
|
|
ctx context.Context,
|
|
request *api.RoomAliasExistsRequest,
|
|
response *api.RoomAliasExistsResponse,
|
|
) error {
|
|
trace, ctx := internal.StartRegion(ctx, "ApplicationServiceRoomAlias")
|
|
defer trace.EndRegion()
|
|
|
|
// Determine which application service should handle this request
|
|
for _, appservice := range a.Cfg.Derived.ApplicationServices {
|
|
if appservice.URL != "" && appservice.IsInterestedInRoomAlias(request.Alias) {
|
|
// The full path to the rooms API, includes hs token
|
|
URL, err := url.Parse(appservice.URL + roomAliasExistsPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
URL.Path += request.Alias
|
|
apiURL := URL.String() + "?access_token=" + appservice.HSToken
|
|
|
|
// Send a request to each application service. If one responds that it has
|
|
// created the room, immediately return.
|
|
req, err := http.NewRequest(http.MethodGet, apiURL, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req = req.WithContext(ctx)
|
|
|
|
resp, err := a.HTTPClient.Do(req)
|
|
if resp != nil {
|
|
defer func() {
|
|
err = resp.Body.Close()
|
|
if err != nil {
|
|
log.WithFields(log.Fields{
|
|
"appservice_id": appservice.ID,
|
|
"status_code": resp.StatusCode,
|
|
}).WithError(err).Error("Unable to close application service response body")
|
|
}
|
|
}()
|
|
}
|
|
if err != nil {
|
|
log.WithError(err).Errorf("Issue querying room alias on application service %s", appservice.ID)
|
|
return err
|
|
}
|
|
switch resp.StatusCode {
|
|
case http.StatusOK:
|
|
// OK received from appservice. Room exists
|
|
response.AliasExists = true
|
|
return nil
|
|
case http.StatusNotFound:
|
|
// Room does not exist
|
|
default:
|
|
// Application service reported an error. Warn
|
|
log.WithFields(log.Fields{
|
|
"appservice_id": appservice.ID,
|
|
"status_code": resp.StatusCode,
|
|
}).Warn("Application service responded with non-OK status code")
|
|
}
|
|
}
|
|
}
|
|
|
|
response.AliasExists = false
|
|
return nil
|
|
}
|
|
|
|
// UserIDExists performs a request to '/users/{userID}' on all known
|
|
// handling application services until one admits to owning the user ID
|
|
func (a *AppServiceQueryAPI) UserIDExists(
|
|
ctx context.Context,
|
|
request *api.UserIDExistsRequest,
|
|
response *api.UserIDExistsResponse,
|
|
) error {
|
|
trace, ctx := internal.StartRegion(ctx, "ApplicationServiceUserID")
|
|
defer trace.EndRegion()
|
|
|
|
// Determine which application service should handle this request
|
|
for _, appservice := range a.Cfg.Derived.ApplicationServices {
|
|
if appservice.URL != "" && appservice.IsInterestedInUserID(request.UserID) {
|
|
// The full path to the rooms API, includes hs token
|
|
URL, err := url.Parse(appservice.URL + userIDExistsPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
URL.Path += request.UserID
|
|
apiURL := URL.String() + "?access_token=" + appservice.HSToken
|
|
|
|
// Send a request to each application service. If one responds that it has
|
|
// created the user, immediately return.
|
|
req, err := http.NewRequest(http.MethodGet, apiURL, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := a.HTTPClient.Do(req.WithContext(ctx))
|
|
if resp != nil {
|
|
defer func() {
|
|
err = resp.Body.Close()
|
|
if err != nil {
|
|
log.WithFields(log.Fields{
|
|
"appservice_id": appservice.ID,
|
|
"status_code": resp.StatusCode,
|
|
}).Error("Unable to close application service response body")
|
|
}
|
|
}()
|
|
}
|
|
if err != nil {
|
|
log.WithFields(log.Fields{
|
|
"appservice_id": appservice.ID,
|
|
}).WithError(err).Error("issue querying user ID on application service")
|
|
return err
|
|
}
|
|
if resp.StatusCode == http.StatusOK {
|
|
// StatusOK received from appservice. User ID exists
|
|
response.UserIDExists = true
|
|
return nil
|
|
}
|
|
|
|
// Log non OK
|
|
log.WithFields(log.Fields{
|
|
"appservice_id": appservice.ID,
|
|
"status_code": resp.StatusCode,
|
|
}).Warn("application service responded with non-OK status code")
|
|
}
|
|
}
|
|
|
|
response.UserIDExists = false
|
|
return nil
|
|
}
|
|
|
|
type thirdpartyResponses interface {
|
|
api.ASProtocolResponse | []api.ASUserResponse | []api.ASLocationResponse
|
|
}
|
|
|
|
func requestDo[T thirdpartyResponses](client *http.Client, url string, response *T) (err error) {
|
|
origURL := url
|
|
// try v1 and unstable appservice endpoints
|
|
for _, version := range []string{"v1", "unstable"} {
|
|
var resp *http.Response
|
|
var body []byte
|
|
asURL := strings.Replace(origURL, "unstable", version, 1)
|
|
resp, err = client.Get(asURL)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
defer resp.Body.Close() // nolint: errcheck
|
|
body, err = io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
return json.Unmarshal(body, &response)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (a *AppServiceQueryAPI) Locations(
|
|
ctx context.Context,
|
|
req *api.LocationRequest,
|
|
resp *api.LocationResponse,
|
|
) error {
|
|
params, err := url.ParseQuery(req.Params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, as := range a.Cfg.Derived.ApplicationServices {
|
|
var asLocations []api.ASLocationResponse
|
|
params.Set("access_token", as.HSToken)
|
|
|
|
url := as.URL + api.ASLocationPath
|
|
if req.Protocol != "" {
|
|
url += "/" + req.Protocol
|
|
}
|
|
|
|
if err := requestDo[[]api.ASLocationResponse](a.HTTPClient, url+"?"+params.Encode(), &asLocations); err != nil {
|
|
log.WithError(err).Error("unable to get 'locations' from application service")
|
|
continue
|
|
}
|
|
|
|
resp.Locations = append(resp.Locations, asLocations...)
|
|
}
|
|
|
|
if len(resp.Locations) == 0 {
|
|
resp.Exists = false
|
|
return nil
|
|
}
|
|
resp.Exists = true
|
|
return nil
|
|
}
|
|
|
|
func (a *AppServiceQueryAPI) User(
|
|
ctx context.Context,
|
|
req *api.UserRequest,
|
|
resp *api.UserResponse,
|
|
) error {
|
|
params, err := url.ParseQuery(req.Params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, as := range a.Cfg.Derived.ApplicationServices {
|
|
var asUsers []api.ASUserResponse
|
|
params.Set("access_token", as.HSToken)
|
|
|
|
url := as.URL + api.ASUserPath
|
|
if req.Protocol != "" {
|
|
url += "/" + req.Protocol
|
|
}
|
|
|
|
if err := requestDo[[]api.ASUserResponse](a.HTTPClient, url+"?"+params.Encode(), &asUsers); err != nil {
|
|
log.WithError(err).Error("unable to get 'user' from application service")
|
|
continue
|
|
}
|
|
|
|
resp.Users = append(resp.Users, asUsers...)
|
|
}
|
|
|
|
if len(resp.Users) == 0 {
|
|
resp.Exists = false
|
|
return nil
|
|
}
|
|
resp.Exists = true
|
|
return nil
|
|
}
|
|
|
|
func (a *AppServiceQueryAPI) Protocols(
|
|
ctx context.Context,
|
|
req *api.ProtocolRequest,
|
|
resp *api.ProtocolResponse,
|
|
) error {
|
|
|
|
// get a single protocol response
|
|
if req.Protocol != "" {
|
|
|
|
a.CacheMu.Lock()
|
|
defer a.CacheMu.Unlock()
|
|
if proto, ok := a.ProtocolCache[req.Protocol]; ok {
|
|
resp.Exists = true
|
|
resp.Protocols = map[string]api.ASProtocolResponse{
|
|
req.Protocol: proto,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
response := api.ASProtocolResponse{}
|
|
for _, as := range a.Cfg.Derived.ApplicationServices {
|
|
var proto api.ASProtocolResponse
|
|
if err := requestDo[api.ASProtocolResponse](a.HTTPClient, as.URL+api.ASProtocolPath+req.Protocol, &proto); err != nil {
|
|
log.WithError(err).Error("unable to get 'protocol' from application service")
|
|
continue
|
|
}
|
|
|
|
if len(response.Instances) != 0 {
|
|
response.Instances = append(response.Instances, proto.Instances...)
|
|
} else {
|
|
response = proto
|
|
}
|
|
}
|
|
|
|
if len(response.Instances) == 0 {
|
|
resp.Exists = false
|
|
return nil
|
|
}
|
|
|
|
resp.Exists = true
|
|
resp.Protocols = map[string]api.ASProtocolResponse{
|
|
req.Protocol: response,
|
|
}
|
|
a.ProtocolCache[req.Protocol] = response
|
|
return nil
|
|
}
|
|
|
|
response := make(map[string]api.ASProtocolResponse, len(a.Cfg.Derived.ApplicationServices))
|
|
|
|
for _, as := range a.Cfg.Derived.ApplicationServices {
|
|
for _, p := range as.Protocols {
|
|
var proto api.ASProtocolResponse
|
|
if err := requestDo[api.ASProtocolResponse](a.HTTPClient, as.URL+api.ASProtocolPath+p, &proto); err != nil {
|
|
log.WithError(err).Error("unable to get 'protocol' from application service")
|
|
continue
|
|
}
|
|
existing, ok := response[p]
|
|
if !ok {
|
|
response[p] = proto
|
|
continue
|
|
}
|
|
existing.Instances = append(existing.Instances, proto.Instances...)
|
|
response[p] = existing
|
|
}
|
|
}
|
|
|
|
if len(response) == 0 {
|
|
resp.Exists = false
|
|
return nil
|
|
}
|
|
|
|
a.CacheMu.Lock()
|
|
defer a.CacheMu.Unlock()
|
|
a.ProtocolCache = response
|
|
|
|
resp.Exists = true
|
|
resp.Protocols = response
|
|
return nil
|
|
}
|