aboutsummaryrefslogtreecommitdiff
path: root/internal/httputil
diff options
context:
space:
mode:
authorNeil Alexander <neilalexander@users.noreply.github.com>2022-08-11 15:29:33 +0100
committerGitHub <noreply@github.com>2022-08-11 15:29:33 +0100
commitc45d0936b59b0eb65f12fe22a3da3690ae0b5494 (patch)
treefffe20dcf4c1d4efd8e6d8b6af1d21ef25a9539c /internal/httputil
parent240ae257deb74b7be8a17500b77d5e1bca56e8f5 (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.go34
-rw-r--r--internal/httputil/internalapi.go93
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)
+}