Skip to content

Commit

Permalink
feat: add HttpService
Browse files Browse the repository at this point in the history
  • Loading branch information
torives committed Jan 17, 2024
1 parent 4887849 commit c7be575
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 0 deletions.
2 changes: 2 additions & 0 deletions cmd/cartesi-rollups-node/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ func main() {
s = append(s, newDispatcher()) // Depends on the state server
s = append(s, newInspectServer()) // Depends on the server-manager/host-runner

s = append(s, newHttpService())

ready := make(chan struct{}, 1)
// logs startup time
go func() {
Expand Down
15 changes: 15 additions & 0 deletions cmd/cartesi-rollups-node/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package main

import (
"fmt"
"net/http"
"os"

"github.com/cartesi/rollups-node/internal/config"
Expand Down Expand Up @@ -326,3 +327,17 @@ func newSupervisorService(s []services.Service) services.SupervisorService {
Services: s,
}
}

func newHttpService() services.HttpService {
handler := http.NewServeMux()
handler.Handle("/healthz", http.HandlerFunc(healthcheckHandler))
return services.HttpService{
Name: "http",
Address: fmt.Sprintf("%v:%v", config.GetCartesiHttpAddress(), getPort(portOffsetProxy)),
Handler: handler,
}
}

func healthcheckHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
56 changes: 56 additions & 0 deletions internal/services/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

package services

import (
"context"
"errors"
"net"
"net/http"

"github.com/cartesi/rollups-node/internal/config"
)

type HttpService struct {
Name string
Address string
Handler http.Handler
}

func (s HttpService) String() string {
return s.Name
}

func (s HttpService) Start(ctx context.Context, ready chan<- struct{}) error {
server := http.Server{
Addr: s.Address,
Handler: s.Handler,
}

listener, err := net.Listen("tcp", s.Address)
if err != nil {
return err
}

config.InfoLogger.Printf("%v: listening at %v\n", s, listener.Addr())
ready <- struct{}{}

done := make(chan error, 1)
go func() {
err := server.Serve(listener)
if !errors.Is(err, http.ErrServerClosed) {
config.WarningLogger.Printf("%v: %v", s, err)
}
done <- err
}()

select {
case err = <-done:
return err
case <-ctx.Done():
ctx, cancel := context.WithTimeout(context.Background(), DefaultServiceTimeout)
defer cancel()
return server.Shutdown(ctx)
}
}
156 changes: 156 additions & 0 deletions internal/services/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

package services

import (
"context"
"fmt"
"io"
"net/http"
"testing"
"time"

"github.com/stretchr/testify/suite"
)

type HttpServiceSuite struct {
suite.Suite
ServicePort int
ServiceAddr string
}

func TestHttpService(t *testing.T) {
suite.Run(t, new(HttpServiceSuite))
}

func (s *HttpServiceSuite) SetupSuite() {
s.ServicePort = 5555
}

func (s *HttpServiceSuite) SetupTest() {
s.ServicePort++
s.ServiceAddr = fmt.Sprintf("127.0.0.1:%v", s.ServicePort)
}

func (s *HttpServiceSuite) TestItStopsWhenContextIsClosed() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

service := HttpService{Name: "http", Address: s.ServiceAddr, Handler: http.NewServeMux()}

result := make(chan error, 1)
ready := make(chan struct{}, 1)
go func() {
result <- service.Start(ctx, ready)
}()

select {
case <-ready:
cancel()
case <-time.After(DefaultServiceTimeout):
s.FailNow("timed out waiting for HttpService to be ready")
}

select {
case err := <-result:
s.Nil(err)
case <-time.After(DefaultServiceTimeout):
s.FailNow("timed out waiting for HttpService to stop")
}
}

func (s *HttpServiceSuite) TestItRespondsToRequests() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

router := http.NewServeMux()
router.HandleFunc("/test", defaultHandler)
service := HttpService{Name: "http", Address: s.ServiceAddr, Handler: router}

result := make(chan error, 1)
ready := make(chan struct{}, 1)
go func() {
result <- service.Start(ctx, ready)
}()

select {
case <-ready:
case <-time.After(DefaultServiceTimeout):
s.FailNow("timed out waiting for HttpService to be ready")
}

resp, err := http.Get(fmt.Sprintf("http://%v/test", s.ServiceAddr))
if err != nil {
s.FailNow(err.Error())
}
s.assertResponse(resp)
}

func (s *HttpServiceSuite) TestItRespondsOngoingRequestsAfterContextIsClosed() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

router := http.NewServeMux()
router.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
// simulate a long-running request
<-time.After(100 * time.Millisecond)
fmt.Fprintf(w, "test")
})
service := HttpService{Name: "http", Address: s.ServiceAddr, Handler: router}

result := make(chan error, 1)
ready := make(chan struct{}, 1)
go func() {
result <- service.Start(ctx, ready)
}()

select {
case <-ready:
case <-time.After(DefaultServiceTimeout):
s.FailNow("timed out wating for HttpService to be ready")
}

clientResult := make(chan ClientResult, 1)
go func() {
resp, err := http.Get(fmt.Sprintf("http://%v/test", s.ServiceAddr))
clientResult <- ClientResult{Response: resp, Error: err}
}()

// wait a bit so server has enough time to start responding the request
<-time.After(200 * time.Millisecond)
cancel()

select {
case res := <-clientResult:
s.Nil(res.Error)
s.assertResponse(res.Response)
err := <-result
s.Nil(err)
case <-result:
s.FailNow("HttpService closed before responding")
}
}

type ClientResult struct {
Response *http.Response
Error error
}

func defaultHandler(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "test")
}

func (s *HttpServiceSuite) assertResponse(resp *http.Response) {
s.Equal(http.StatusOK, resp.StatusCode)

defer resp.Body.Close()

bytes, err := io.ReadAll(resp.Body)
if err != nil {
s.FailNow("failed to read response body. ", err)
}
s.Equal([]byte("test"), bytes)
}

0 comments on commit c7be575

Please sign in to comment.