mirror of
https://github.com/hoernschen/dendrite.git
synced 2025-07-31 13:22:46 +00:00
Generic-based internal HTTP API (#2626)
* Generic-based internal HTTP API (tested out on a few endpoints in the federation API) * Add `PerformInvite` * More tweaks * Fix metric name * Fix LookupStateIDs * Lots of changes to clients * Some serverside stuff * Some error handling * Use paths as metric names * Revert "Use paths as metric names" This reverts commit a9323a6a343f5ce6461a2e5bd570fe06465f1b15. * Namespace metric names * Remove duplicate entry * Remove another duplicate entry * Tweak error handling * Some more tweaks * Update error behaviour * Some more error tweaking * Fix API path for `PerformDeleteKeys` * Fix another path * Tweak federation client proxying * Fix another path * Don't return typed nils * Some more tweaks, not that it makes any difference * Tweak federation client proxying * Maybe fix the key backup test
This commit is contained in:
parent
240ae257de
commit
c45d0936b5
57 changed files with 1535 additions and 2418 deletions
|
@ -19,19 +19,21 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
)
|
||||
|
||||
// PostJSON performs a POST request with JSON on an internal HTTP API
|
||||
func PostJSON(
|
||||
// PostJSON performs a POST request with JSON on an internal HTTP API.
|
||||
// The error will match the errtype if returned from the remote API, or
|
||||
// will be a different type if there was a problem reaching the API.
|
||||
func PostJSON[reqtype, restype any, errtype error](
|
||||
ctx context.Context, span opentracing.Span, httpClient *http.Client,
|
||||
apiURL string, request, response interface{},
|
||||
apiURL string, request *reqtype, response *restype,
|
||||
) error {
|
||||
jsonBytes, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
|
@ -69,17 +71,23 @@ func PostJSON(
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
var errorBody struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if _, ok := response.(*api.PerformKeyBackupResponse); ok { // TODO: remove this, once cross-boundary errors are a thing
|
||||
return nil
|
||||
}
|
||||
if msgerr := json.NewDecoder(res.Body).Decode(&errorBody); msgerr == nil {
|
||||
return fmt.Errorf("internal API: %d from %s: %s", res.StatusCode, apiURL, errorBody.Message)
|
||||
}
|
||||
return fmt.Errorf("internal API: %d from %s", res.StatusCode, apiURL)
|
||||
var body []byte
|
||||
body, err = io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.NewDecoder(res.Body).Decode(response)
|
||||
if res.StatusCode != http.StatusOK {
|
||||
if len(body) == 0 {
|
||||
return fmt.Errorf("HTTP %d from %s (no response body)", res.StatusCode, apiURL)
|
||||
}
|
||||
var reserr errtype
|
||||
if err = json.Unmarshal(body, reserr); err != nil {
|
||||
return fmt.Errorf("HTTP %d from %s", res.StatusCode, apiURL)
|
||||
}
|
||||
return reserr
|
||||
}
|
||||
if err = json.Unmarshal(body, response); err != nil {
|
||||
return fmt.Errorf("json.Unmarshal: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
93
internal/httputil/internalapi.go
Normal file
93
internal/httputil/internalapi.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// 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 httputil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/matrix-org/util"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
type InternalAPIError struct {
|
||||
Type string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e InternalAPIError) Error() string {
|
||||
return fmt.Sprintf("internal API returned %q error: %s", e.Type, e.Message)
|
||||
}
|
||||
|
||||
func MakeInternalRPCAPI[reqtype, restype any](metricsName string, f func(context.Context, *reqtype, *restype) error) http.Handler {
|
||||
return MakeInternalAPI(metricsName, func(req *http.Request) util.JSONResponse {
|
||||
var request reqtype
|
||||
var response restype
|
||||
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
if err := f(req.Context(), &request, &response); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: &InternalAPIError{
|
||||
Type: reflect.TypeOf(err).String(),
|
||||
Message: fmt.Sprintf("%s", err),
|
||||
},
|
||||
}
|
||||
}
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: &response,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func MakeInternalProxyAPI[reqtype, restype any](metricsName string, f func(context.Context, *reqtype) (*restype, error)) http.Handler {
|
||||
return MakeInternalAPI(metricsName, func(req *http.Request) util.JSONResponse {
|
||||
var request reqtype
|
||||
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
response, err := f(req.Context(), &request)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: err,
|
||||
}
|
||||
}
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: response,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func CallInternalRPCAPI[reqtype, restype any](name, url string, client *http.Client, ctx context.Context, request *reqtype, response *restype) error {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, name)
|
||||
defer span.Finish()
|
||||
|
||||
return PostJSON[reqtype, restype, InternalAPIError](ctx, span, client, url, request, response)
|
||||
}
|
||||
|
||||
func CallInternalProxyAPI[reqtype, restype any, errtype error](name, url string, client *http.Client, ctx context.Context, request *reqtype) (restype, error) {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, name)
|
||||
defer span.Finish()
|
||||
|
||||
var response restype
|
||||
return response, PostJSON[reqtype, restype, errtype](ctx, span, client, url, request, &response)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue