Sitelet https://github.com/ethereum-optimism/optimism/commit/4ebfca5ea3f8d10d675ff3f2c31bebc945d71ada
Skip to content

Commit 4ebfca5

Browse files
authored
op-service: RPC server wraps http-util server now, and factor out RPC handler (#14491)
* op-service: RPC server wraps http-util server now, and factor out RPC handler * op-service: fix sub-test name
1 parent ed1e341 commit 4ebfca5

15 files changed

Lines changed: 773 additions & 420 deletions

File tree

‎op-alt-da/daserver.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"strconv"
1515
"time"
1616

17-
"github.com/ethereum-optimism/optimism/op-service/rpc"
17+
"github.com/ethereum-optimism/optimism/op-service/httputil"
1818
"github.com/ethereum/go-ethereum/common/hexutil"
1919
"github.com/ethereum/go-ethereum/log"
2020
)
@@ -30,7 +30,7 @@ type DAServer struct {
3030
log log.Logger
3131
endpoint string
3232
store KVStore
33-
tls *rpc.ServerTLSConfig
33+
tls *httputil.ServerTLSConfig
3434
httpServer *http.Server
3535
listener net.Listener
3636
useGenericComm bool

‎op-node/p2p/gossip_test.go‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,11 @@ func TestVerifyBlockSignatureWithRemoteSigner(t *testing.T) {
129129
"127.0.0.1",
130130
0,
131131
"test",
132-
oprpc.WithAPIs([]rpc.API{
133-
{
134-
Namespace: "opsigner",
135-
Service: remoteSigner,
136-
},
137-
}),
138132
)
133+
server.AddAPI(rpc.API{
134+
Namespace: "opsigner",
135+
Service: remoteSigner,
136+
})
139137

140138
require.NoError(t, server.Start())
141139
defer func() {

‎op-node/rollup/interop/managed/system.go‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,12 @@ func NewManagedMode(log log.Logger, cfg *rollup.Config, addr string, port int, j
6565
out.srv = rpc.NewServer(addr, port, "v0.0.0",
6666
rpc.WithWebsocketEnabled(),
6767
rpc.WithLogger(log),
68-
rpc.WithJWTSecret(jwtSecret[:]),
69-
rpc.WithAPIs([]gethrpc.API{
70-
{
71-
Namespace: "interop",
72-
Service: &InteropAPI{backend: out},
73-
Authenticated: true,
74-
},
75-
}))
68+
rpc.WithJWTSecret(jwtSecret[:]))
69+
out.srv.AddAPI(gethrpc.API{
70+
Namespace: "interop",
71+
Service: &InteropAPI{backend: out},
72+
Authenticated: true,
73+
})
7674
return out
7775
}
7876

‎op-service/httputil/http.go‎

Lines changed: 0 additions & 20 deletions
This file was deleted.

‎op-service/httputil/options.go‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package httputil
2+
3+
import (
4+
"crypto/tls"
5+
"net/http"
6+
7+
optls "github.com/ethereum-optimism/optimism/op-service/tls"
8+
)
9+
10+
type config struct {
11+
// listenAddr is the configured address to listen to when started.
12+
// use listener.Addr to retrieve the address when online.
13+
listenAddr string
14+
15+
tls *ServerTLSConfig
16+
17+
handler http.Handler
18+
19+
httpOpts []HTTPOption
20+
}
21+
22+
func (c *config) ApplyOptions(opts ...Option) {
23+
for _, opt := range opts {
24+
opt(c)
25+
}
26+
}
27+
28+
// Option is a general config option.
29+
type Option func(cfg *config)
30+
31+
// HTTPOption applies a change to an HTTP server, just before standup.
32+
// HTTPOption options are be re-executed on server shutdown/startup cycles,
33+
// for each new underlying Go *http.Server instance.
34+
type HTTPOption func(config *http.Server) error
35+
36+
func WithHTTPOptions(options ...HTTPOption) Option {
37+
return func(cfg *config) {
38+
cfg.httpOpts = append(cfg.httpOpts, options...)
39+
}
40+
}
41+
42+
func WithMaxHeaderBytes(max int) HTTPOption {
43+
return func(srv *http.Server) error {
44+
srv.MaxHeaderBytes = max
45+
return nil
46+
}
47+
}
48+
49+
type ServerTLSConfig struct {
50+
Config *tls.Config
51+
CLIConfig *optls.CLIConfig // paths to certificate and key files
52+
}
53+
54+
func WithServerTLS(tlsCfg *ServerTLSConfig) Option {
55+
return func(cfg *config) {
56+
cfg.tls = tlsCfg
57+
}
58+
}

‎op-service/httputil/server.go‎

Lines changed: 137 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,65 @@ import (
66
"fmt"
77
"net"
88
"net/http"
9-
"sync/atomic"
9+
"sync"
10+
"time"
1011
)
1112

1213
// HTTPServer wraps a http.Server, while providing conveniences
1314
// like exposing the running state and address.
15+
//
16+
// It can be started with HTTPServer.Start and closed with
17+
// HTTPServer.Stop, HTTPServer.Close and HTTPServer.Shutdown (convenience functions for different gracefulness).
18+
//
19+
// The addr contains both host and port. A 0 port may be used to make the system bind to an available one.
20+
// The resulting address can be retrieved with HTTPServer.Addr or HTTPServer.HTTPEndpoint.
21+
//
22+
// The server may be started, stopped and started back up.
1423
type HTTPServer struct {
24+
// mu is the lock used for bringing the server online/offline, and accessing the address of the server.
25+
mu sync.RWMutex
26+
27+
// listener that the server is bound to. Nil if online.
1528
listener net.Listener
16-
srv *http.Server
17-
closed atomic.Bool
29+
30+
srv *http.Server
31+
32+
// used as BaseContext of the http.Server
33+
srvCtx context.Context
34+
srvCancel context.CancelFunc
35+
36+
config *config
1837
}
1938

20-
// HTTPOption applies a change to an HTTP server
21-
type HTTPOption func(srv *HTTPServer) error
39+
// NewHTTPServer creates an HTTPServer that serves the given HTTP handler.
40+
// The server is inactive and has to be started explicitly.
41+
func NewHTTPServer(addr string, handler http.Handler, opts ...Option) *HTTPServer {
42+
cfg := &config{
43+
listenAddr: addr,
44+
tls: nil,
45+
handler: handler,
46+
httpOpts: nil,
47+
}
48+
cfg.ApplyOptions(opts...)
49+
return &HTTPServer{config: cfg}
50+
}
2251

23-
func StartHTTPServer(addr string, handler http.Handler, opts ...HTTPOption) (*HTTPServer, error) {
24-
listener, err := net.Listen("tcp", addr)
25-
if err != nil {
26-
return nil, fmt.Errorf("failed to bind to address %q: %w", addr, err)
52+
func StartHTTPServer(addr string, handler http.Handler, opts ...Option) (*HTTPServer, error) {
53+
out := NewHTTPServer(addr, handler, opts...)
54+
return out, out.Start()
55+
}
56+
57+
// Start starts the server, and checks if it comes online fully.
58+
func (s *HTTPServer) Start() error {
59+
s.mu.Lock()
60+
defer s.mu.Unlock()
61+
if s.srv != nil {
62+
return errors.New("already have existing server")
2763
}
2864

2965
srvCtx, srvCancel := context.WithCancel(context.Background())
3066
srv := &http.Server{
31-
Handler: handler,
67+
Handler: s.config.handler,
3268
ReadTimeout: DefaultTimeouts.ReadTimeout,
3369
ReadHeaderTimeout: DefaultTimeouts.ReadHeaderTimeout,
3470
WriteTimeout: DefaultTimeouts.WriteTimeout,
@@ -37,28 +73,56 @@ func StartHTTPServer(addr string, handler http.Handler, opts ...HTTPOption) (*HT
3773
return srvCtx
3874
},
3975
}
40-
out := &HTTPServer{listener: listener, srv: srv}
41-
for _, opt := range opts {
42-
if err := opt(out); err != nil {
76+
77+
if s.config.tls != nil {
78+
srv.TLSConfig = s.config.tls.Config
79+
}
80+
81+
for _, opt := range s.config.httpOpts {
82+
if err := opt(srv); err != nil {
4383
srvCancel()
44-
return nil, errors.Join(fmt.Errorf("failed to apply HTTP option: %w", err), listener.Close())
84+
return fmt.Errorf("failed to apply HTTP option: %w", err)
4585
}
4686
}
47-
go func() {
48-
err := out.srv.Serve(listener)
87+
88+
listener, err := net.Listen("tcp", s.config.listenAddr)
89+
if err != nil {
4990
srvCancel()
50-
// no error, unless ErrServerClosed (or unused base context closes, or unused http2 config error)
51-
if errors.Is(err, http.ErrServerClosed) {
52-
out.closed.Store(true)
91+
return fmt.Errorf("failed to bind to address %q: %w", s.config.listenAddr, err)
92+
}
93+
s.listener = listener
94+
95+
s.srv = srv
96+
s.srvCtx = srvCtx
97+
s.srvCancel = srvCancel
98+
99+
// cap of 1, to not block on non-immediate shutdown
100+
errCh := make(chan error, 1)
101+
go func() {
102+
if s.config.tls != nil {
103+
errCh <- s.srv.ServeTLS(s.listener, "", "")
53104
} else {
54-
panic(fmt.Errorf("unexpected serve error: %w", err))
105+
errCh <- s.srv.Serve(s.listener)
55106
}
56107
}()
57-
return out, nil
108+
109+
// verify that the server comes up
110+
standupTimer := time.NewTimer(10 * time.Millisecond)
111+
defer standupTimer.Stop()
112+
113+
select {
114+
case err := <-errCh:
115+
s.cleanup()
116+
return fmt.Errorf("http server failed: %w", err)
117+
case <-standupTimer.C:
118+
return nil
119+
}
58120
}
59121

60122
func (s *HTTPServer) Closed() bool {
61-
return s.closed.Load()
123+
s.mu.RLock()
124+
defer s.mu.RUnlock()
125+
return s.srv == nil
62126
}
63127

64128
// Stop is a convenience method to gracefully shut down the server, but force-close if the ctx is cancelled.
@@ -73,28 +137,73 @@ func (s *HTTPServer) Stop(ctx context.Context) error {
73137
return nil
74138
}
75139

140+
func (s *HTTPServer) cleanup() {
141+
s.srv = nil
142+
s.listener = nil
143+
s.srvCtx = nil
144+
s.srvCancel = nil
145+
}
146+
76147
// Shutdown shuts down the HTTP server and its listener,
77148
// but allows active connections to close gracefully.
78149
// If the function exits due to a ctx cancellation the listener is closed but active connections may remain,
79150
// a call to Close() can force-close any remaining active connections.
80151
func (s *HTTPServer) Shutdown(ctx context.Context) error {
152+
s.mu.Lock()
153+
defer s.mu.Unlock()
154+
if s.srv == nil {
155+
return nil
156+
}
157+
s.srvCancel()
81158
// closes the underlying listener too.
82-
return s.srv.Shutdown(ctx)
159+
err := s.srv.Shutdown(ctx)
160+
if err != nil {
161+
return err
162+
}
163+
s.cleanup()
164+
return nil
83165
}
84166

85167
// Close force-closes the HTTPServer, its listener, and all its active connections.
86168
func (s *HTTPServer) Close() error {
169+
s.mu.Lock()
170+
defer s.mu.Unlock()
171+
if s.srv == nil {
172+
return nil
173+
}
174+
s.srvCancel()
87175
// closes the underlying listener too
88-
return s.srv.Close()
176+
err := s.srv.Close()
177+
if err != nil {
178+
return err
179+
}
180+
s.cleanup()
181+
return nil
89182
}
90183

184+
// Addr returns the address that the server is listening on.
185+
// It returns nil if the server is not online.
91186
func (s *HTTPServer) Addr() net.Addr {
187+
s.mu.RLock()
188+
defer s.mu.RUnlock()
189+
if s.listener == nil {
190+
return nil
191+
}
92192
return s.listener.Addr()
93193
}
94194

95-
func WithMaxHeaderBytes(max int) HTTPOption {
96-
return func(srv *HTTPServer) error {
97-
srv.srv.MaxHeaderBytes = max
98-
return nil
195+
// HTTPEndpoint returns the http(s) endpoint the server is serving.
196+
// It returns an empty string if the server is not online.
197+
func (s *HTTPServer) HTTPEndpoint() string {
198+
s.mu.RLock()
199+
defer s.mu.RUnlock()
200+
if s.listener == nil {
201+
return ""
202+
}
203+
addr := s.listener.Addr().String()
204+
if s.config.tls != nil {
205+
return "https://" + addr
206+
} else {
207+
return "http://" + addr
99208
}
100209
}

0 commit comments

Comments
 (0)