diff options
author | Neil Alexander <neilalexander@users.noreply.github.com> | 2022-08-11 15:29:33 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-11 15:29:33 +0100 |
commit | c45d0936b59b0eb65f12fe22a3da3690ae0b5494 (patch) | |
tree | fffe20dcf4c1d4efd8e6d8b6af1d21ef25a9539c /internal/httputil | |
parent | 240ae257deb74b7be8a17500b77d5e1bca56e8f5 (diff) |
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
Diffstat (limited to 'internal/httputil')
-rw-r--r-- | internal/httputil/http.go | 34 | ||||
-rw-r--r-- | internal/httputil/internalapi.go | 93 |
2 files changed, 114 insertions, 13 deletions
diff --git a/internal/httputil/http.go b/internal/httputil/http.go index 4527e2b9..1e07ee33 100644 --- a/internal/httputil/http.go +++ b/internal/httputil/http.go @@ -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 } + var body []byte + body, err = io.ReadAll(res.Body) + if err != nil { + return err + } if res.StatusCode != http.StatusOK { - var errorBody struct { - Message string `json:"message"` + if len(body) == 0 { + return fmt.Errorf("HTTP %d from %s (no response body)", res.StatusCode, apiURL) } - if _, ok := response.(*api.PerformKeyBackupResponse); ok { // TODO: remove this, once cross-boundary errors are a thing - return nil + var reserr errtype + if err = json.Unmarshal(body, reserr); err != nil { + return fmt.Errorf("HTTP %d from %s", res.StatusCode, apiURL) } - 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) + return reserr + } + if err = json.Unmarshal(body, response); err != nil { + return fmt.Errorf("json.Unmarshal: %w", err) } - return json.NewDecoder(res.Body).Decode(response) + return nil } diff --git a/internal/httputil/internalapi.go b/internal/httputil/internalapi.go new file mode 100644 index 00000000..385092d9 --- /dev/null +++ b/internal/httputil/internalapi.go @@ -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) +} |