Skip to content

Commit

Permalink
Merge pull request #6 from rocket-pool/net-socket
Browse files Browse the repository at this point in the history
Updates from Betas
  • Loading branch information
jclapis authored May 7, 2024
2 parents 0e82b79 + 5d76379 commit 82c5400
Show file tree
Hide file tree
Showing 18 changed files with 571 additions and 116 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/commits.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Taken from https://github.com/marketplace/actions/block-fixup-commit-merge?version=v2.0.0
# Updated to use newer ubuntu and checkout action
name: Git Checks

on: [pull_request]

jobs:
block-fixup:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Block Fixup Commit Merge
uses: 13rac1/[email protected]
46 changes: 46 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Taken from https://github.com/golangci/golangci-lint-action
name: golangci-lint
on:
push:
tags:
- v*
branches:
- main
pull_request:
permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
# pull-requests: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v4
with:
go-version: 1.21.8
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest

# Optional: working directory, useful for monorepos
# working-directory: somedir

# Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0

# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true

# Optional: if set to true then the all caching functionality will be complete disabled,
# takes precedence over all other caching options.
# skip-cache: true

# Optional: if set to true then the action don't cache or restore ~/go/pkg.
# skip-pkg-cache: true

# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
# skip-build-cache: true
19 changes: 19 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: NMC Unit Tests
on:
push:
tags:
- v*
branches:
- main
pull_request:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 1.21.8
- run: go test ./...
60 changes: 19 additions & 41 deletions api/client/client.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package client

import (
"errors"
"fmt"
"io"
"io/fs"
"log/slog"
"net/http"
"net/url"
"os"
"strconv"
"strings"

Expand All @@ -20,22 +17,6 @@ import (
"github.com/ethereum/go-ethereum/common"
)

const (
jsonContentType string = "application/json"
)

// IRequester is an interface for making HTTP requests to a specific subroute on the NMC server
type IRequester interface {
// The human-readable requester name (for logging)
GetName() string

// The name of the subroute to send requests to
GetRoute() string

// Context to hold settings and utilities the requester should use
GetContext() *RequesterContext
}

// Submit a GET request to the API server
func SendGetRequest[DataType any](r IRequester, method string, requestName string, args map[string]string) (*types.ApiResponse[DataType], error) {
if args == nil {
Expand All @@ -49,15 +30,9 @@ func SendGetRequest[DataType any](r IRequester, method string, requestName strin
}

// Submit a GET request to the API server
func RawGetRequest[DataType any](context *RequesterContext, path string, params map[string]string) (*types.ApiResponse[DataType], error) {
// Make sure the socket exists
_, err := os.Stat(context.socketPath)
if errors.Is(err, fs.ErrNotExist) {
return nil, fmt.Errorf("the socket at [%s] does not exist - please start the service and try again", context.socketPath)
}

func RawGetRequest[DataType any](context IRequesterContext, path string, params map[string]string) (*types.ApiResponse[DataType], error) {
// Create the request
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/%s", context.base, path), nil)
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s", context.GetAddressBase(), path), nil)
if err != nil {
return nil, fmt.Errorf("error creating HTTP request: %w", err)
}
Expand All @@ -70,10 +45,10 @@ func RawGetRequest[DataType any](context *RequesterContext, path string, params
req.URL.RawQuery = values.Encode()

// Debug log
context.logger.Debug("API Request", slog.String(log.MethodKey, http.MethodGet), slog.String(log.QueryKey, req.URL.String()))
context.GetLogger().Debug("API Request", slog.String(log.MethodKey, http.MethodGet), slog.String(log.QueryKey, req.URL.String()))

// Run the request
resp, err := context.client.Do(req)
resp, err := context.SendRequest(req)
return HandleResponse[DataType](context, resp, path, err)
}

Expand All @@ -93,25 +68,28 @@ func SendPostRequest[DataType any](r IRequester, method string, requestName stri
}

// Submit a POST request to the API server
func RawPostRequest[DataType any](context *RequesterContext, path string, body string) (*types.ApiResponse[DataType], error) {
// Make sure the socket exists
_, err := os.Stat(context.socketPath)
if errors.Is(err, fs.ErrNotExist) {
return nil, fmt.Errorf("the socket at [%s] does not exist - please start the service and try again", context.socketPath)
func RawPostRequest[DataType any](context IRequesterContext, path string, body string) (*types.ApiResponse[DataType], error) {
// Create the request
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/%s", context.GetAddressBase(), path), strings.NewReader(body))
if err != nil {
return nil, fmt.Errorf("error creating HTTP request: %w", err)
}
req.Header.Set("Content-Type", jsonContentType)

// Debug log
context.logger.Debug("API Request", slog.String(log.MethodKey, http.MethodPost), slog.String(log.PathKey, path), slog.String(log.BodyKey, body))
context.GetLogger().Debug("API Request", slog.String(log.MethodKey, http.MethodPost), slog.String(log.PathKey, path), slog.String(log.BodyKey, body))

resp, err := context.client.Post(fmt.Sprintf("http://%s/%s", context.base, path), jsonContentType, strings.NewReader(body))
// Run the request
resp, err := context.SendRequest(req)
return HandleResponse[DataType](context, resp, path, err)
}

// Processes a response to a request
func HandleResponse[DataType any](context *RequesterContext, resp *http.Response, path string, err error) (*types.ApiResponse[DataType], error) {
func HandleResponse[DataType any](context IRequesterContext, resp *http.Response, path string, err error) (*types.ApiResponse[DataType], error) {
if err != nil {
return nil, fmt.Errorf("error requesting %s: %w", path, err)
}
logger := context.GetLogger()

// Read the body
defer resp.Body.Close()
Expand All @@ -122,26 +100,26 @@ func HandleResponse[DataType any](context *RequesterContext, resp *http.Response

// Handle 404s specially since they won't have a JSON body
if resp.StatusCode == http.StatusNotFound {
context.logger.Debug("API Response (raw)", slog.String(log.CodeKey, resp.Status), slog.String(log.BodyKey, string(bytes)))
logger.Debug("API Response (raw)", slog.String(log.CodeKey, resp.Status), slog.String(log.BodyKey, string(bytes)))
return nil, fmt.Errorf("route '%s' not found", path)
}

// Deserialize the response into the provided type
var parsedResponse types.ApiResponse[DataType]
err = json.Unmarshal(bytes, &parsedResponse)
if err != nil {
context.logger.Debug("API Response (raw)", slog.String(log.CodeKey, resp.Status), slog.String(log.BodyKey, string(bytes)))
logger.Debug("API Response (raw)", slog.String(log.CodeKey, resp.Status), slog.String(log.BodyKey, string(bytes)))
return nil, fmt.Errorf("error deserializing response to %s: %w", path, err)
}

// Check if the request failed
if resp.StatusCode != http.StatusOK {
context.logger.Debug("API Response", slog.String(log.PathKey, path), slog.String(log.CodeKey, resp.Status), slog.String("err", parsedResponse.Error))
logger.Debug("API Response", slog.String(log.PathKey, path), slog.String(log.CodeKey, resp.Status), slog.String("err", parsedResponse.Error))
return nil, fmt.Errorf(parsedResponse.Error)
}

// Debug log
context.logger.Debug("API Response", slog.String(log.BodyKey, string(bytes)))
logger.Debug("API Response", slog.String(log.BodyKey, string(bytes)))

return &parsedResponse, nil
}
Expand Down
46 changes: 0 additions & 46 deletions api/client/context.go

This file was deleted.

67 changes: 67 additions & 0 deletions api/client/net-context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package client

import (
"context"
"log/slog"
"net"
"net/http"
"net/http/httptrace"
"net/url"
)

// The context passed into a requester
type NetworkRequesterContext struct {
// The base address and route for API calls
apiUrl *url.URL

// An HTTP client for sending requests
client *http.Client

// Logger to print debug messages to
logger *slog.Logger

// Tracer for HTTP requests
tracer *httptrace.ClientTrace
}

// Creates a new API client requester context for network-based
// traceOpts is optional. If nil, it will not be used.
func NewNetworkRequesterContext(apiUrl *url.URL, log *slog.Logger, tracer *httptrace.ClientTrace) *NetworkRequesterContext {
requesterContext := &NetworkRequesterContext{
apiUrl: apiUrl,
logger: log,
tracer: tracer,
client: &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return net.Dial("tcp", apiUrl.Host)
},
},
},
}

return requesterContext
}

// Get the base of the address used for submitting server requests
func (r *NetworkRequesterContext) GetAddressBase() string {
return r.apiUrl.String()
}

// Get the logger for the context
func (r *NetworkRequesterContext) GetLogger() *slog.Logger {
return r.logger
}

// Set the logger for the context
func (r *NetworkRequesterContext) SetLogger(logger *slog.Logger) {
r.logger = logger
}

// Send an HTTP request to the server
func (r *NetworkRequesterContext) SendRequest(request *http.Request) (*http.Response, error) {
if r.tracer != nil {
request = request.WithContext(httptrace.WithClientTrace(request.Context(), r.tracer))
}
return r.client.Do(request)
}
5 changes: 5 additions & 0 deletions api/client/settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package client

const (
jsonContentType string = "application/json"
)
33 changes: 33 additions & 0 deletions api/client/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package client

import (
"log/slog"
"net/http"
)

// IRequester is an interface for making HTTP requests to a specific subroute on the NMC server
type IRequester interface {
// The human-readable requester name (for logging)
GetName() string

// The name of the subroute to send requests to
GetRoute() string

// Context to hold settings and utilities the requester should use
GetContext() IRequesterContext
}

// IRequester is an interface for making HTTP requests to a specific subroute on the NMC server
type IRequesterContext interface {
// Get the base of the address used for submitting server requests
GetAddressBase() string

// Get the logger for the context
GetLogger() *slog.Logger

// Set the logger for the context
SetLogger(*slog.Logger)

// Send an HTTP request to the server
SendRequest(request *http.Request) (*http.Response, error)
}
Loading

0 comments on commit 82c5400

Please sign in to comment.